Go Down

Topic: Reading MIDI, converting to polyphonic square wave with Tone library (Read 2425 times) previous topic - next topic

DerStrom8

Jul 23, 2014, 02:10 am Last Edit: Jul 23, 2014, 02:54 am by DerStrom8 Reason: 1
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.

Code: [Select]
#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

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.

DerStrom8

#2
Jul 23, 2014, 02:43 am Last Edit: Jul 23, 2014, 02:55 am by DerStrom8 Reason: 1

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: http://pastebin.com/EZBwuUPT

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.

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: 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.
http://arduino.cc/en/reference/tone

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.

DerStrom8

#4
Jul 23, 2014, 03:35 am Last Edit: Jul 23, 2014, 03:38 am by DerStrom8 Reason: 1

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.
http://arduino.cc/en/reference/tone

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....?

DerStrom8

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....).

Code: [Select]
#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);
  }
}

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  :smiley-red:

DerStrom8


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  :smiley-red:


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!

DerStrom8

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:

Quote

#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.

DerStrom8

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

Grumpy_Mike

Quote
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.

DerStrom8


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.

Grumpy_Mike

Quote
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.

DerStrom8


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

Grumpy_Mike

Quote
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.

Quote
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.

Go Up