Reading MIDI, converting to polyphonic square wave with Tone library

Hi folks,

I'm working on a project using my Arduino Uno to read in MIDI via the serial connection and play a polyphonic tone that mimics the MIDI input. The problem I am having is this: It only seems to be able to play three notes (one after another), and then it stops playing altogether. The arduino is still receiving data (I see the RX LED on the board flashing) but it doesn't play any tones. Polyphonic sounds get very distorted and don't stop when I release the keys. Hoping someone can give me a hand here. I have excluded some of the code as it is irrelevant to the MIDI--only mode 2 (MIDI) is giving me problems.

#include <avr/pgmspace.h>
#include "Tone.h"

// Output for PWM
int PWMPin = 6;
// External PWM enable
int PWMPower = 5;
// 7-segment pins
int A = 13;
int B = 12;
int C = 11;
int D = 10;
int E = 9;
int F = 8;
int G = 7;

volatile int mode;  // 0 = Output Off, 1 = Manual PWM, 2 = PC MIDI, 3 = External MIDI

//Audio output pins
//Using Analog pins simply becaues they were open
int ch0out = A0;
int ch1out = A1;
int ch2out = A2;
int ch3out = A3;
int ch4out = A4;
int ch5out = A5;

//MIDI notes
Tone ch0;
Tone ch1;
Tone ch2;
Tone ch3;
Tone ch4;
Tone ch5;

// Number of modes
#define modeNum 3
// Highest channel (0-15)
// Will eventually use an array to keep track
// of discrete channels we want to play
#define channelNum 5

// midi commands
#define MIDI_CMD_NOTE_OFF 0x80
#define MIDI_CMD_NOTE_ON 0x90

/* Probably don't need these for my project
#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
*/

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

// store note frequencies
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};

void setup() {

  // Power on signal to external PWM circuitry
  pinMode(PWMPower, OUTPUT);
  
  // 7-segment display pins
  pinMode(A, OUTPUT);
  pinMode(B, OUTPUT);
  pinMode(C, OUTPUT);
  pinMode(D, OUTPUT);
  pinMode(E, OUTPUT);
  pinMode(F, OUTPUT);
  pinMode(G, OUTPUT);
  
  // Interrupt to watch for mode-change button press
  attachInterrupt(0, changeMode, RISING);
  mode = 0;
  
  displayMode();
  
  // Make sure power signal to external PWM is off
  digitalWrite(PWMPower, LOW);
}

void loop () {
   
  if (mode == 0) {
  }
  else if (mode == 1) {
  }
  
  /*************************************************
   ** This is where the MIDI signal is processed  **
   *************************************************/
  
  else if (mode == 2) {
    static byte note;                                       // Variable to store note number
    static byte state;                                      // Variable to store state (Byte 1, Byte 2)
    static byte lastByte;                                   // Variable to store previous byte value
    static int cmd = MIDI_IGNORE;                           // Variable to store current command
    static int channel;                                     // Variable to store current channel number
  
    while (Serial.available()) {
      
      // read the incoming byte:
      byte thisByte = Serial.read();                        //Read incoming byte
  
      if (thisByte & 0b10000000) {                          //Is this a command byte?
        if (channel <= channelNum) {                        // Is this one of our channels?
          cmd = thisByte & 0xF0;                            //Save the command
          channel = thisByte & 0x0F;                        //Save the channel number
        } else {
          cmd = MIDI_IGNORE;                                // Otherwise, ignore message
        }
        state = MIDI_STATE_BYTE1;                           // Prepare for Byte 1 (note)
      } else if ((state == MIDI_STATE_BYTE1) && (cmd != MIDI_IGNORE)) {    // Is this data Byte 1?
        if ( cmd==MIDI_CMD_NOTE_OFF ) {                     //If command is for NOTE OFF
          state = MIDI_STATE_BYTE2;                         // expect to receive a velocity byte
        } else if ( cmd == MIDI_CMD_NOTE_ON ) {             // If command is for NOTE ON
          if (channel == 0x00) ch0.begin(ch0out);           // prepare to play note on specified channel
          else if (channel == 0x01) ch1.begin(ch1out);
          else if (channel == 0x02) ch2.begin(ch2out);
          else if (channel == 0x03) ch3.begin(ch3out);
          else if (channel == 0x04) ch4.begin(ch4out);
          else if (channel == 0x05) ch5.begin(ch5out);
        }
        lastByte=thisByte;                                  // save the current note
        state = MIDI_STATE_BYTE2;                           // prepare for Byte2 (velocity)
      } else if ((state == MIDI_STATE_BYTE2) && (cmd != MIDI_IGNORE)) {    // Is this data for Byte 2 (velocity)?
        if (cmd == MIDI_CMD_NOTE_OFF) {
          if (channel == 0x00) ch0.stop();                  // turn off note on current channel
          else if (channel == 0x01) ch1.stop();
          else if (channel == 0x02) ch2.stop();
          else if (channel == 0x03) ch3.stop();
          else if (channel == 0x04) ch4.stop();
          else if (channel == 0x05) ch5.stop();
        } else if (cmd == MIDI_CMD_NOTE_ON) {
          if (thisByte != 0) {                              // if we're actually playing a note
            note = lastByte;                                // get note number
            if (channel == 0x00) ch0.play((unsigned int)pgm_read_word(&frequency[note]));        // Play specified note on specified channel
            else if (channel == 0x01) ch1.play((unsigned int)pgm_read_word(&frequency[note]));
            else if (channel == 0x02) ch2.play((unsigned int)pgm_read_word(&frequency[note]));
            else if (channel == 0x03) ch3.play((unsigned int)pgm_read_word(&frequency[note]));
            else if (channel == 0x04) ch4.play((unsigned int)pgm_read_word(&frequency[note]));
            else if (channel == 0x05) ch5.play((unsigned int)pgm_read_word(&frequency[note]));
          }
        }
        state = MIDI_STATE_BYTE1; // message data complete
      }
    }
  }
  
  /**********************************
   ** End MIDI Processing Section  **
   **********************************/
}

void changeMode(){
  static unsigned long last_interrupt_time = 0;
  unsigned long interrupt_time = millis();
  // If interrupts come faster than 200ms, assume it's a bounce and ignore
  if (interrupt_time - last_interrupt_time > 200) 
  {
    Serial.end();
    mode++;
    if (mode > modeNum) {
      mode = 0;
    }
  }
  last_interrupt_time = interrupt_time;
  
  if (mode == 0) {
    stopAllChannels();
  } else if (mode == 1) {
    stopAllChannels();
    digitalWrite(PWMPower, HIGH);
  } else if (mode == 2) {
    digitalWrite(PWMPower, LOW);
    Serial.begin(57600);
  } else if (mode == 3) {
    stopAllChannels();
    digitalWrite(PWMPower, LOW);
  }
  displayMode();
}

//For stopping all MIDI channels
void stopAllChannels() {
  ch0.stop();
  ch1.stop();
  ch2.stop();
  ch3.stop();
  ch4.stop();
  ch5.stop();
}

void displayMode(){
  if (mode == 0) {
    digitalWrite(A, HIGH);
    digitalWrite(B, HIGH);
    digitalWrite(C, HIGH);
    digitalWrite(D, HIGH);
    digitalWrite(E, HIGH);
    digitalWrite(F, HIGH);
    digitalWrite(G, LOW);
  }
  if (mode == 1) {
    digitalWrite(A, LOW);
    digitalWrite(B, HIGH);
    digitalWrite(C, HIGH);
    digitalWrite(D, LOW);
    digitalWrite(E, LOW);
    digitalWrite(F, LOW);
    digitalWrite(G, LOW);
  }
  else if (mode == 2) {
    digitalWrite(A, HIGH);
    digitalWrite(B, HIGH);
    digitalWrite(C, LOW);
    digitalWrite(D, HIGH);
    digitalWrite(E, HIGH);
    digitalWrite(F, LOW);
    digitalWrite(G, HIGH);
  }
  else if (mode == 3) {
    digitalWrite(A, HIGH);
    digitalWrite(B, HIGH);
    digitalWrite(C, HIGH);
    digitalWrite(D, HIGH);
    digitalWrite(E, LOW);
    digitalWrite(F, LOW);
    digitalWrite(G, HIGH);
  }
}

If anyone sees any clear issues, please let me know!
Thanks,
Matt

It is only a part of a sketch.
When you write a post, you can see above the text input field a '#' button, those are tags that you can place around a sketch.

Peter_n:
It is only a part of a sketch.
When you write a post, you can see above the text input field a '#' button, those are tags that you can place around a sketch.

Yes, the entire sketch will not fit because of the 9600 character limit. It is a long sketch. I tried posting the whole thing at first but it wouldn't let me, so I picked out what I thought was the critical sections.

Here is the full sketch pasted into pastebin: #include <avr/pgmspace.h>#include "Tone.h"// Output for PWMint PWMPin = - Pastebin.com

Let me know if you need Tone.h and Tone.cpp. They are not mine though, they are out there on the internet somewhere XD

EDIT: Sorry about that, looks like the code tags prevent the code from adding to the character limit. My original post has been fixed to display the entire sketch.

Yes, I woul like a link to the Tone library if it is not the default Arduino Tone library.
Is it a special polyphone library ? Those claim the complete Arduino and interrupts are often disabled.

Perhaps this one: https://code.google.com/p/rogue-code/wiki/ToneLibraryDocumentation
That might be outdated, and I think there is no advantage over the Arduino Tone library.
tone() - Arduino Reference

To be honest, I can't figure out the flow of the code. But what you describe could be an interrupt problem. And you do use an interrupt. And you do funny things in the interrupt. That are 3 red flags for the interrupt.

Why do you even try to Serial.end() the serial connection ?
The Serial library uses buffers and interrupts, so a call to Serial.end() from inside an interrupt routine might be bad.
The same goes for Tone.stop() that you call from inside an interrupt.

You should do a Serial.begin() in the setup() function and keep the Serial open.
The Serial.end() is for very special cases and only for advanced users. I have never used it myself.

Peter_n:
Yes, I woul like a link to the Tone library if it is not the default Arduino Tone library.
Is it a special polyphone library ? Those claim the complete Arduino and interrupts are often disabled.

Perhaps this one: Google Code Archive - Long-term storage for Google Code Project Hosting.
That might be outdated, and I think there is no advantage over the Arduino Tone library.
tone() - Arduino Reference

To be honest, I can't figure out the flow of the code. But what you describe could be an interrupt problem. And you do use an interrupt. And you do funny things in the interrupt. That are 3 red flags for the interrupt.

Why do you even try to Serial.end() the serial connection ?
The Serial library uses buffers and interrupts, so a call to Serial.end() from inside an interrupt routine might be bad.
The same goes for Tone.stop() that you call from inside an interrupt.

You should do a Serial.begin() in the setup() function and keep the Serial open.
The Serial.end() is for very special cases and only for advanced users. I have never used it myself.

That's great information, thank you.

The link you posted for the Tone is correct--that is the library I am using.

I used the Serial.end() in my experimentation to see if it fixed the problem, but it did not. Tone.end() is required (I believe) to stop the tone once it's started. I believe that's the key difference between the standard tone() function in Arduino and the Tone library--this one allows a note to continue to play until a stop() command is sent to it.

I also was thinking it could be an interrupt issue--I've been running into a few of those here and there--but I have yet to figure out what the issue is. One of my interrupts is tripped when a push-button, which is used to change the mode, is pressed. It is connected to Interrupt 0. However, since the mode never seems to be changed by accident, I didn't think it was tripping the interrupt when it wasn't supposed to. It could still be a timer issue though, I suppose....?

Sorry for the double-post, but I thought I'd post the (slightly) updated code which takes your suggestions into account. I removed the Serial.end() statements and added the Serial.begin() statement inside the setup() (I did have it there originally, not sure what happened to it....).

#include <avr/pgmspace.h>
#include "Tone.h"

// Output for PWM
int PWMPin = 6;
// External PWM enable
int PWMPower = 5;
// 7-segment pins
int A = 13;
int B = 12;
int C = 11;
int D = 10;
int E = 9;
int F = 8;
int G = 7;

volatile int mode;  // 0 = Output Off, 1 = Manual PWM, 2 = PC MIDI, 3 = External MIDI

//Audio output pins
//Using Analog pins simply becaues they were open
int ch0out = A0;
int ch1out = A1;
int ch2out = A2;
int ch3out = A3;
int ch4out = A4;
int ch5out = A5;

//MIDI notes
Tone ch0;
Tone ch1;
Tone ch2;
Tone ch3;
Tone ch4;
Tone ch5;

// Number of modes
#define modeNum 3
// Highest channel (0-15)
// Will eventually use an array to keep track
// of discrete channels we want to play
#define channelNum 5

// midi commands
#define MIDI_CMD_NOTE_OFF 0x80
#define MIDI_CMD_NOTE_ON 0x90

/* Probably don't need these for a Tesla coil
 #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
 */

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

// store note frequencies
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};

void setup() {

  // Power on signal to external PWM circuitry
  pinMode(PWMPower, OUTPUT);

  // 7-segment display pins
  pinMode(A, OUTPUT);
  pinMode(B, OUTPUT);
  pinMode(C, OUTPUT);
  pinMode(D, OUTPUT);
  pinMode(E, OUTPUT);
  pinMode(F, OUTPUT);
  pinMode(G, OUTPUT);

  // Interrupt to watch for mode-change button press
  attachInterrupt(0, changeMode, RISING);
  mode = 0;
  displayMode();

  // Make sure power signal to external PWM is off
  digitalWrite(PWMPower, LOW);
  Serial.begin(57600);
}

void loop () {

  if (mode == 0) {
  }
  else if (mode == 1) {
  }

  /*************************************************
   ** This is where the MIDI signal is processed  **
   *************************************************/

  else if (mode == 2) {
    static byte note;                                       // Variable to store note number
    static byte state;                                      // Variable to store state (Byte 1, Byte 2)
    static byte lastByte;                                   // Variable to store previous byte value
    static int cmd = MIDI_IGNORE;                           // Variable to store current command
    static int channel;                                     // Variable to store current channel number

    while (Serial.available()) {

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

      if (thisByte & 0b10000000) {                          //Is this a command byte?
        if (channel <= channelNum) {                        // Is this one of our channels?
          cmd = thisByte & 0xF0;                            //Save the command
          channel = thisByte & 0x0F;                        //Save the channel number
        } 
        else {
          cmd = MIDI_IGNORE;                                // Otherwise, ignore message
        }
        state = MIDI_STATE_BYTE1;                           // Prepare for Byte 1 (note)
      } 
      else if ((state == MIDI_STATE_BYTE1) && (cmd != MIDI_IGNORE)) {    // Is this data Byte 1 (note)?
        if ( cmd==MIDI_CMD_NOTE_OFF ) {                     //If command is for NOTE OFF
          state = MIDI_STATE_BYTE2;                         // expect to receive a velocity byte
        } 
        else if ( cmd == MIDI_CMD_NOTE_ON ) {               // If command is for NOTE ON
          if (channel == 0x00) ch0.begin(ch0out);           // prepare to play note on specified channel
          else if (channel == 0x01) ch1.begin(ch1out);
          else if (channel == 0x02) ch2.begin(ch2out);
          else if (channel == 0x03) ch3.begin(ch3out);
          else if (channel == 0x04) ch4.begin(ch4out);
          else if (channel == 0x05) ch5.begin(ch5out);
        }
        lastByte=thisByte;                                  // save the current note
        state = MIDI_STATE_BYTE2;                           // prepare for Byte2 (velocity)
      } 
      else if ((state == MIDI_STATE_BYTE2) && (cmd != MIDI_IGNORE)) {    // Is this data Byte 2 (velocity)?
        if (cmd == MIDI_CMD_NOTE_OFF) {
          if (channel == 0x00) ch0.stop();                  // turn off note on current channel
          else if (channel == 0x01) ch1.stop();
          else if (channel == 0x02) ch2.stop();
          else if (channel == 0x03) ch3.stop();
          else if (channel == 0x04) ch4.stop();
          else if (channel == 0x05) ch5.stop();
        } 
        else if (cmd == MIDI_CMD_NOTE_ON) {
          if (thisByte != 0) {                              // if we're actually playing a note
            note = lastByte;                                // get note number
            if (channel == 0x00) ch0.play((unsigned int)pgm_read_word(&frequency[note]));        // Play specified note on specified channel
            else if (channel == 0x01) ch1.play((unsigned int)pgm_read_word(&frequency[note]));
            else if (channel == 0x02) ch2.play((unsigned int)pgm_read_word(&frequency[note]));
            else if (channel == 0x03) ch3.play((unsigned int)pgm_read_word(&frequency[note]));
            else if (channel == 0x04) ch4.play((unsigned int)pgm_read_word(&frequency[note]));
            else if (channel == 0x05) ch5.play((unsigned int)pgm_read_word(&frequency[note]));
          }
        }
        state = MIDI_STATE_BYTE1; // message data complete
      }
    }
  }

  /**********************************
   ** End MIDI Processing Section  **
   **********************************/
}

void changeMode(){
  static unsigned long last_interrupt_time = 0;
  unsigned long interrupt_time = millis();
  // If interrupts come faster than 200ms, assume it's a bounce and ignore
  if (interrupt_time - last_interrupt_time > 200) 
  {
    mode++;
    if (mode > modeNum) {
      mode = 0;
    }
  }
  last_interrupt_time = interrupt_time;

  if (mode == 0) {
    stopAllChannels();
  } 
  else if (mode == 1) {
    stopAllChannels();
    digitalWrite(PWMPower, HIGH);
  } 
  else if (mode == 2) {
    digitalWrite(PWMPower, LOW);
  } 
  else if (mode == 3) {
    stopAllChannels();
    digitalWrite(PWMPower, LOW);
  }
  displayMode();
}

//For stopping all MIDI channels
void stopAllChannels() {
  ch0.stop();
  ch1.stop();
  ch2.stop();
  ch3.stop();
  ch4.stop();
  ch5.stop();
}

void displayMode(){
  if (mode == 0) {
    digitalWrite(A, HIGH);
    digitalWrite(B, HIGH);
    digitalWrite(C, HIGH);
    digitalWrite(D, HIGH);
    digitalWrite(E, HIGH);
    digitalWrite(F, HIGH);
    digitalWrite(G, LOW);
  }
  if (mode == 1) {
    digitalWrite(A, LOW);
    digitalWrite(B, HIGH);
    digitalWrite(C, HIGH);
    digitalWrite(D, LOW);
    digitalWrite(E, LOW);
    digitalWrite(F, LOW);
    digitalWrite(G, LOW);
  }
  else if (mode == 2) {
    digitalWrite(A, HIGH);
    digitalWrite(B, HIGH);
    digitalWrite(C, LOW);
    digitalWrite(D, HIGH);
    digitalWrite(E, HIGH);
    digitalWrite(F, LOW);
    digitalWrite(G, HIGH);
  }
  else if (mode == 3) {
    digitalWrite(A, HIGH);
    digitalWrite(B, HIGH);
    digitalWrite(C, HIGH);
    digitalWrite(D, HIGH);
    digitalWrite(E, LOW);
    digitalWrite(F, LOW);
    digitalWrite(G, HIGH);
  }
}

The Arduino Tone library can play only one tone and the library you use can do more at the same time.
Do you at some point use more than 2 tones ? Can you try with a maximum of two tones ?
If that doesn't help, I'm out of ideas :blush:

Peter_n:
The Arduino Tone library can play only one tone and the library you use can do more at the same time.
Do you at some point use more than 2 tones ? Can you try with a maximum of two tones ?
If that doesn't help, I'm out of ideas :blush:

That is actually the exact reason why I chose this particular Tone library. originally I used the simple tone() function, but I had to select just a single channel to play, which often didn't work out very well.

When receiving MIDI data I do expect to receive more than 2 tones, yes. One example would be two instruments playing at the same time (even though they're on different channels), and another example would be a chord on a single instrument. My experimentation to this point has consisted of the Hairless MIDI-Serial bridge and the Virtual MIDI Piano Keyboard (VMPK) to send MIDI data over USB to the Arduino. The first three notes I play work fine (assuming they're played one at a time), but for some reason, after that third note, it simply stops playing anything. Changing the channel on the VMPK does not help either, so it's not a channel-specific problem.

I tried to comment the code the best I could to help explain how it works. It's based on some code written by Greg Kennedy (who posted it here on the Arduino forums somewhere), though his code was designed to use the Arduino tone library, and thus was not suitable for polyphonic MIDI inputs. Effectively, the code retrieves the current byte being sent over Serial and determines whether it is a command byte or a data byte. If it is a command byte, it stores the command and the channel. Otherwise, if it's not a command byte, it checks to see which byte it is--Byte 1 (note data) or Byte 2 (velocity data). The values are saved and then the Tone library plays the specified note on the specified channel.

Once again, don't worry about the rest of the code--there are several modes, and three of them have nothing to do with this MIDI input. I am only concerned with Mode 2 at this point, which is the mode that I have placed comments around (at the beginning and end) to highlight the code in question.

Changing the "channelNum" to "1" (2 channels) does not appear to help.

It actually occurred to me, I may be confusing multiple tones from one channel with multiple tones from multiple channels. Or perhaps it's just the terminology I'm using in the code is misleading--using "ch1" instead of, say, "tone1"? Just curious what your take on it is. "ch1.stop()" does not actually stop the channel, it just stops the specified tone. If I am not mistaken, I should be able to access all of the channels, but I can only play 6 tones at once (between all of the channels).

Right now I'm just thinking out loud, and I'm hoping it might spark some ideas.

Thanks for looking it over, I do appreciate your help!

Sorry for making another double-post, but I think I'd better start over again from the beginning.

I have the code to read in each byte from the serial (MIDI) signal. That part is all fine and dandy. The part I need help with is using the Tone library (not the standard Arduino tone, but the one mentioned earlier) to play multiple sounds at once that mimic the MIDI input. Effectively I need to convert the MIDI to polyphonic square wave audio.

If anyone needs to see how the library is normally used, here's an example:

#include "Tone.h"

Tone tone1;
Tone tone2;
Tone tone3;

void setup()
{
tone1.begin(A0);
tone2.begin(A1);
tone3.begin(A2);
tone1.play(NOTE_C4);
tone2.play(NOTE_E4);
tone3.play(NOTE_G4);
}

void loop()
{
}

I only have 6 I/O ports to work with here, all the rest are being used elsewhere.

Hi all,

I've realized I need to completely re-think this entire program. I must not have an on-time of greater than 1mS, which means in order to get the range of frequencies I need (standard note scales), I will need to use PWM (and when I say PWM I mean actual pulse width modulation, not just what's used for analogWrite() ). Therefore I won't be able to use any of the Tone libraries, because they all operate with a set 50% duty cycle.

I have access to the note frequency, so I have to work solely from that frequency. The output should also be on one pin, if possible, because I would like to avoid external mixing circuitry.

Any ideas? I've been struggling with this for days, if not weeks now, and I have become quite frustrated.

Regards,
Matt

I will need to use PWM (and when I say PWM I mean actual pulse width modulation, not just what's used for analogWrite() ).

So why is PWM not what is produced by analogWrite?

You will here no difference in frequency when you alter the duty cycle.
You have to decide how many channels of polyphony you want and code accordingly. You could use an interrupt driven phase accumulation pointer to derive the frequencies but if you just have a digital output there is no way to mix several tones on one pin.

Grumpy_Mike:
So why is PWM not what is produced by analogWrite?

You will here no difference in frequency when you alter the duty cycle.
You have to decide how many channels of polyphony you want and code accordingly. You could use an interrupt driven phase accumulation pointer to derive the frequencies but if you just have a digital output there is no way to mix several tones on one pin.

When I said PWM that's not used by analogWrite, I meant that I need to be able to vary the frequency. If I am not mistaken, analogWrite uses a set frequency, whereas my code will need to take a specified frequency and also have a duty cycle that prevents the on-time from exceeding 1mS.

I figure 3 channels of polyphony should be enough, though if it's simple, the more the better.

Would it be plausible to use an output on individual pins and simply mix them with resistors on the outputs? If this is the case, then I can have up to 6 channels of polyphony (since that's the number of available IO pins I have).

I will also need to index a specific note that is playing in order to turn it off when the Arduino receives a MIDI "Note Off" command for that note. Generating the polyphonic tone AND being able to index an individual tone in order to turn it off is what's frustrating me.

When I said PWM that's not used by analogWrite, I meant that I need to be able to vary the frequency

If the frequency changes then it is not PWM, it is as simple as that.

Yes you can use external resistors to mix outputs, that is no problem.

As to indexing you have an array with an entry for each tone you are producing. In the note on function you search for a free channel and mark that as being in use by setting that array element to the note number. Then start that oscillator playing.
When you get a note off message you search the array for that note number, when you find it you stop the note and clear the note number, thus indicating it is a free for use again.

Grumpy_Mike:
If the frequency changes then it is not PWM, it is as simple as that.

Yes you can use external resistors to mix outputs, that is no problem.

As to indexing you have an array with an entry for each tone you are producing. In the note on function you search for a free channel and mark that as being in use by setting that array element to the note number. Then start that oscillator playing.
When you get a note off message you search the array for that note number, when you find it you stop the note and clear the note number, thus indicating it is a free for use again.

First of all, is there any way this forum could get an auto-save for body content? I just typed out an in-depth response, but when I went to submit it it told me my session had timed out and all of my content was gone.

Anyway, let's see if I can re-type this XD

For the PWM part: You can have PWM while also changing the frequency. I have built circuits that do just this using a 555 timer and a 393 comparator. There are two potentiometers--one adjusts the frequency, and one adjusts the duty cycle. Changing either one does not affect the other. This is what I'm looking for--I want to be able to select a frequency (based on the specified note), but also be able to adjust the duty cycle (apply PWM) in order to prevent the on-time from exceeding 1mS. My point was that I cannot use the built-in PWM functionality because it has a set frequency that is not easily changeable, especially not to the extent where I can select specific notes.

The array idea is one I tried implementing a while back, but I ran into a roadblock, and I cannot remember why. I may try it again, because it should be very simple to do.

Thanks for the help!
Regards,
Matt

First of all, is there any way this forum could get an auto-save for body content?

No that is annoying and it happens to all of us. The real solution is to routinely copy your reply before you hit post.

You can have PWM while also changing the frequency...................

No you can't.
What you describe is not PWM. It is PPM, that is pulse position modulation.

Grumpy_Mike:
No that is annoying and it happens to all of us. The real solution is to routinely copy your reply before you hit post.

Oh well, it was worth a try.... XD

You can have PWM while also changing the frequency...................

No you can't.
What you describe is not PWM. It is PPM, that is pulse position modulation.

How is setting the frequency yourself and then adjusting the pulse width different from having a previously set frequency and then adjusting the pulse width? It's still PWM, regardless of the frequency it's set at. Therefore, setting the signal to a specific frequency (note), and then adjusting the pulse width is still PWM (not the frequency adjustment part, but the pulse width adjustment part).

Anyway, I think I have the indexing down. I have the notes stored in an array. I'm thinking I would like a function that is triggered almost continuously (not literally, but apparently) to check the values in the array and play the notes, but make sure that it has a maximum on-time of 1mS. I expect I would want to use the timer1 interrupt to constantly check on the array, but when it comes to playing the notes, I'm not sure how I'd do that. Different timer, I guess?

Thanks for the help. I think we're just getting caught up on the terminology. I'm not saying adjusting frequency is part of PWM. I'm saying that you can set the frequency yourself, and then apply PWM.

Regards,
Matt

but also be able to adjust the duty cycle (apply PWM) in order to prevent the on-time from exceeding 1mS.

Why is this?
Altering the pulse width in a PPM system, ( you might like to think it is PWM but the rest of the world will disagree and it is important to use words in the same way as every one else ) will affect the timbre of the note, why do you want to do this?
It seems like you are over complicating matters with this.

Grumpy_Mike:

but also be able to adjust the duty cycle (apply PWM) in order to prevent the on-time from exceeding 1mS.

Why is this?
Altering the pulse width in a PPM system, ( you might like to think it is PWM but the rest of the world will disagree and it is important to use words in the same way as every one else ) will affect the timbre of the note, why do you want to do this?
It seems like you are over complicating matters with this.

PPM is different, and is not what I am referring to. I know the difference between PPM and PWM, and I am still referring to PWM.

If I am not mistaken, the standard frequency used for most PWM pins on the Uno is 490 Hz. I intend to change the frequency before applying PWM. For example, for an A4 note, I would set the frequency to 440 Hz. Then I can use PWM to reduce or increase the duty cycle (at that set 440 Hz). It is still PWM, it just happens to be based on a different frequency. I'm not sure if I can make this any clearer. I am not new to waveforms or modulation, and I do know what I am talking about. Being able to express my thoughts, however, is not my strongpoint... :~ XD

I thought I had mentioned this before, but I need to prevent the on-time from exceeding 1mS because this project will eventually be used to control a Tesla coil. If the on-time is too long, it will cause severe overheating in the drive transistors, which could damage them. Therefore, regardless of the note, I need the duty cycle to be set so that any given pulse is not longer than 1mS.

Sorry once again for the confusion. I hope you can understand what I'm trying to say.
Regards,
Matt

EDIT: It occurred to me, I should probably refer to the duty cycle rather than PWM. I want to set the duty cycle in such a way as to prevent the on-time from exceeding 1mS regarless of the frequency. I think that may be where we're getting confused.

I thought I had mentioned this before, but I need to prevent the on-time from exceeding 1mS because this project will eventually be used to control a Tesla coil

The only mention of Tesla in the thread was a comment it the code saying you probably didn't need this for a Tesla coil, so you didn't exactly go out of your way to mention it.

The problem with using the timers in the PWM mode for doing PPM is that the resolution of them will not allow you to derive all the musical notes you want.

What is wrong with the phase accumulation method of generating notes?

Grumpy_Mike:
The only mention of Tesla in the thread was a comment it the code saying you probably didn't need this for a Tesla coil, so you didn't exactly go out of your way to mention it.

The problem with using the timers in the PWM mode for doing PPM is that the resolution of them will not allow you to derive all the musical notes you want.

What is wrong with the phase accumulation method of generating notes?

In that case, my apologies. I have three threads going on on three different forums involving different parts of this project, so I must have mentioned it on one of them. Sorry about that--I am not keeping up very well :stuck_out_tongue:

I'm not familiar with phase accumulation, though it has been brought up a couple of times. I'll have to look it up. Do you have any recommended links? If not, that's fine--I'll do some searching to see what I can find.

Thanks again!
Matt