Go Down

Topic: Midi Drums, having trouble coding polyphonic serial (Read 947 times) previous topic - next topic

nickevasion

hello, Im making a MIDI drum kit, but im having trouble with polyphonic hits, basically triggering multiple MIDI notes at the same time. Im using the MIDI serial library. Here is my code.

Code: [Select]

/*
   This examples shows how to make a simple seven keys MIDI keyboard with volume control

   Created: 4/10/2015
   Author: Arturo Guadalupi <a.guadalupi@arduino.cc>
   
   http://www.arduino.cc/en/Tutorial/MidiDevice
*/

#include "MIDIUSB.h"
#include "PitchToNote.h"
#define NUM_PIEZOS  6

//Defines analog pins
const uint8_t piezo1 = A0;
const uint8_t piezo2 = A1;
const uint8_t piezo3 = A2;
const uint8_t piezo4 = A3;
const uint8_t piezo5 = A4;
const uint8_t piezo6 = A5;

//Arrays for playnotes()
const uint8_t piezos[NUM_PIEZOS] = {piezo1, piezo2, piezo3, piezo4, piezo5, piezo6};
const byte notePitches[NUM_PIEZOS] = {48, 50, 52, 53, 55, 57};

uint8_t notesTime[NUM_PIEZOS];
uint8_t hitPiezos[NUM_PIEZOS] = {0x00,0x00,0x00,0x00,0x00,0x00};
uint8_t previousPiezos[NUM_PIEZOS] = {0x00,0x00,0x00,0x00,0x00,0x00,};
uint8_t thresh = 100;
uint8_t voltage = 0;
uint8_t intensity = 0;
void setup() {
  Serial1.begin(9600);
}


void loop() {
  readPiezo();
  readIntensity();
  polyphonic();
  playNotes();
 
}

// First parameter is the event type (0x0B = control change).
// Second parameter is the event type, combined with the channel.
// Third parameter is the control number number (0-119).
// Fourth parameter is the control value (0-127).

void controlChange(byte channel, byte control, byte value) {
  midiEventPacket_t event = {0x0B, 0xB0 | channel, control, value};
  MidiUSB.sendMIDI(event);
}



// First parameter is the event type (0x09 = note on, 0x08 = note off).
// Second parameter is note-on/note-off, combined with the channel.
// Channel can be anything between 0-15. Typically reported to the user as 1-16.
// Third parameter is the note number (48 = middle C).
// Fourth parameter is the velocity (64 = normal, 127 = fastest).

void noteOn(byte channel, byte pitch, byte velocity) {
  midiEventPacket_t noteOn = {0x09, 0x90 | channel, pitch, velocity};
  MidiUSB.sendMIDI(noteOn);
 
}

void noteOff(byte channel, byte pitch, byte velocity) {
  midiEventPacket_t noteOff = {0x08, 0x80 | channel, pitch, velocity};
  MidiUSB.sendMIDI(noteOff);
}

//Reads Piezos. Stores triggered piezos into a bit
void readPiezo()
{
  for (int i = 0; i < NUM_PIEZOS; i++)
  {
    if (analogRead(piezos[i]) >= thresh)
    {
      bitWrite(hitPiezos[i], i, 1);
      delay(50);
      voltage = piezos[i];        //Value for Intensity
    }
    else
      bitWrite(hitPiezos[i], i, 0);
  }
}

//Sets Velocity
void readIntensity()
{
  int val = analogRead(voltage);
  intensity = (uint8_t) (map(val, 0, 1023, 0, 127));
}

//Reads single note bits, plays single midi notes
void playNotes()
{
  for (int i = 0,j = 1; i < NUM_PIEZOS; i++, j++)
  {
    if (bitRead(hitPiezos[i], i) != bitRead(previousPiezos[i], i))
    {
      if (bitRead(hitPiezos[i], i))
      {
        bitWrite(previousPiezos[i], i , 1);
        noteOn(0, notePitches[i], intensity);
        MidiUSB.flush();
        Serial.println("Knock");
      }
     
   
      else
      {
        bitWrite(previousPiezos[i], i , 0);
        noteOff(0, notePitches[i], 0);
        MidiUSB.flush();
     
      }
      }
     
      } 
           }
     
//Reads simultanious bits, and plays simultanious notes
  void polyphonic()
  {
    if (bitRead(hitPiezos[0], 0) != bitRead(previousPiezos[0], 0) && (bitRead(hitPiezos[1], 1) != bitRead(previousPiezos[1], 1)))
 
    { 
      bitWrite(previousPiezos[0], 0 , 1);
        noteOn(0, notePitches[0], intensity);
        MidiUSB.flush();
        bitWrite(previousPiezos[1], 1 , 1);
        noteOn(0, notePitches[1], intensity);
        MidiUSB.flush();
        Serial.println("Double knock");
    }
        else
        {
        bitWrite(previousPiezos[0], 0 , 0);
        noteOff(0, notePitches[0], 0);
        MidiUSB.flush();
        bitWrite(previousPiezos[1], 1 , 0);
        noteOff(0, notePitches[1], 0);
        MidiUSB.flush();
       
        }


It all works fine, but when i hit two piezos, i only get one or zero MIDI notes in Ableton. I do however get the correct message on the serial monitor.
This code triggers two notes, but with one piezo
Code: [Select]
for (int i = 0,j = 1; i < NUM_PIEZOS; i++, j++)
  {
    if (bitRead(hitPiezos[i], i) != bitRead(previousPiezos[i], i))
    {
      if (bitRead(hitPiezos[i], i))
      {
        bitWrite(previousPiezos[i], i , 1);
        noteOn(0, notePitches[i], intensity);
        MidiUSB.flush();
        Serial.println("Knock");
        bitWrite(previousPiezos[j], j , 1);
        noteOn(0, notePitches[j], intensity);
        MidiUSB.flush();
        Serial.println("Knock");
      }

so im not sure why the polyphonic() section i wrote would not work.

I'm sure this had been done in the past, i just cant seem to find any viable code examples.

thank you in advance
-Nick

Grumpy_Mike

Quote
I do however get the correct message on the serial monitor.
So how come you can use the serial monitor as well as MIDI? It is normally one or the other. Try dropping the serial print.
What Arduino are you using?

nickevasion

I am using a Leonardo, and I just added the Serial.println so i could see the piezos being triggered outside of Ableton live. Also I just tried the code without them, and I report no changes.

Grumpy_Mike

#3
Jan 10, 2018, 06:16 am Last Edit: Jan 10, 2018, 06:17 am by Grumpy_Mike
I think your code is not right. You first read all the sensors, setting a single bit in an 8 bit array. Why do this and not just set it to the value one? This will not stop it from working but is odd.
But then you read just a single sensor again for the intensity, do you not need to read them all here? And also store that intensity in a variable for later?

Then in the play note function you send a lot of note off messages for those sensors you are not triggering this time. As the loop is very fast then this turns off the note vertually as soon as you trigger it. If it is percussion sound you don't need a note off.

So I can not see how this code can hope to work.

nickevasion

#4
Jan 10, 2018, 05:02 pm Last Edit: Jan 10, 2018, 06:08 pm by nickevasion
Wellllllll
Idk why the original author of this script chose to store bit values instead of whole numbers. This code is a modified version of this midi device project
https://www.arduino.cc/en/Tutorial/MidiDevice
I just changed a few things so that if would read piezos vs buttons, and instead of a potentiometer to set the velocity, I changed the intensity function so that it would change the velocity depending on how hard the piezo is hit.
Reading a single velocity might actually be my issuse as a noteOn  message with 0 velocity is read in MIDI as a noteOff message.
I'll mess around with it after work.
Do you have any ideas on how the play note function should be coded as to avoid constant NoteOff messages?

Grumpy_Mike

#5
Jan 10, 2018, 06:40 pm Last Edit: Jan 10, 2018, 06:41 pm by Grumpy_Mike
The problem with trying to read in input voltage and using it to set the velocity is that with any simple code it will trigger the note at any voltage over the threshold. What you should do is once you find the input above the threshold you should keep on reading it until it reaches a peak and only then trigger the note with the velocity derived from the peak threshold. Once a note is triggered you have three options,

1) To send the note off once the sensor voltage has dropped below the threshold. But that rather cuts off cymbals.
2) Not to send a note off at all because percussion sounds don't hang like some other notes.
3) To put the note in a queue time stamped with the millis time of the note on and send the note off when the millis has advanced from that time stamp by some set amount, like four seconds. This is done in a separate function called at the start of the loop function.

But the whole code structure is wrong, you should scan each sensor and do stuff when it finds a sensor over the threshold and not try to acquire the data in several lumps like it does now.

Quote
This code is a modified version of this midi device project
https://www.arduino.cc/en/Tutorial/MidiDevice
There are some quite poor tutorials on this site and it is almost impossible to get them changed even when they are downright wrong, let alone just poor.

nickevasion

Awesome this seems like a huge help, I did see what your talking about with the peak in a YouTube video, I'll tinker with it this week and post the results

nickevasion

ok so, skratch that code.

This code i found on YouTube, uses the velocity peak you were talking about, and does not use the MIDI cereal library, so it can be used with any board if you run hairless and MIDIloop.

Code: [Select]

/*
 * Copyright (c) 2015 Evan Kale
 * Email: EvanKale91@gmail.com
 * Website: www.ISeeDeadPixel.com
 *          www.evankale.blogspot.ca
 *
 * This file is part of ArduinoMidiDrums.
 *
 * ArduinoMidiDrums is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

//Piezo defines
#define NUM_PIEZOS 6
#define SNARE_THRESHOLD 30     //anything < TRIGGER_THRESHOLD is treated as 0
#define LTOM_THRESHOLD 30
#define RTOM_THRESHOLD 30
#define LCYM_THRESHOLD 100
#define RCYM_THRESHOLD 100
#define KICK_THRESHOLD 50
#define START_SLOT 0     //first analog slot of piezos

//MIDI note defines for each trigger
#define SNARE_NOTE 70
#define LTOM_NOTE 71
#define RTOM_NOTE 72
#define LCYM_NOTE 73
#define RCYM_NOTE 74
#define KICK_NOTE 75

//MIDI defines
#define NOTE_ON_CMD 0x90
#define NOTE_OFF_CMD 0x80
#define MAX_MIDI_VELOCITY 127

//MIDI baud rate
#define SERIAL_RATE 9600

//Program defines
//ALL TIME MEASURED IN MILLISECONDS
#define SIGNAL_BUFFER_SIZE 100
#define PEAK_BUFFER_SIZE 30
#define MAX_TIME_BETWEEN_PEAKS 20
#define MIN_TIME_BETWEEN_NOTES 50

//map that holds the mux slots of the piezos
unsigned short slotMap[NUM_PIEZOS];

//map that holds the respective note to each piezo
unsigned short noteMap[NUM_PIEZOS];

//map that holds the respective threshold to each piezo
unsigned short thresholdMap[NUM_PIEZOS];

//Ring buffers to store analog signal and peaks
short currentSignalIndex[NUM_PIEZOS];
short currentPeakIndex[NUM_PIEZOS];
unsigned short signalBuffer[NUM_PIEZOS][SIGNAL_BUFFER_SIZE];
unsigned short peakBuffer[NUM_PIEZOS][PEAK_BUFFER_SIZE];

boolean noteReady[NUM_PIEZOS];
unsigned short noteReadyVelocity[NUM_PIEZOS];
boolean isLastPeakZeroed[NUM_PIEZOS];

unsigned long lastPeakTime[NUM_PIEZOS];
unsigned long lastNoteTime[NUM_PIEZOS];

void setup()
{
  Serial.begin(SERIAL_RATE);
 
  //initialize globals
  for(short i=0; i<NUM_PIEZOS; ++i)
  {
    currentSignalIndex[i] = 0;
    currentPeakIndex[i] = 0;
    memset(signalBuffer[i],0,sizeof(signalBuffer[i]));
    memset(peakBuffer[i],0,sizeof(peakBuffer[i]));
    noteReady[i] = false;
    noteReadyVelocity[i] = 0;
    isLastPeakZeroed[i] = true;
    lastPeakTime[i] = 0;
    lastNoteTime[i] = 0;   
    slotMap[i] = START_SLOT + i;
  }
 
  thresholdMap[0] = KICK_THRESHOLD;
  thresholdMap[1] = RTOM_THRESHOLD;
  thresholdMap[2] = RCYM_THRESHOLD;
  thresholdMap[3] = LCYM_THRESHOLD;
  thresholdMap[4] = SNARE_THRESHOLD;
  thresholdMap[5] = LTOM_THRESHOLD; 
 
  noteMap[0] = KICK_NOTE;
  noteMap[1] = RTOM_NOTE;
  noteMap[2] = RCYM_NOTE;
  noteMap[3] = LCYM_NOTE;
  noteMap[4] = SNARE_NOTE;
  noteMap[5] = LTOM_NOTE; 
}

void loop()
{
  unsigned long currentTime = millis();
 
  for(short i=0; i<NUM_PIEZOS; ++i)
  {
    //get a new signal from analog read
    unsigned short newSignal = analogRead(slotMap[i]);
    signalBuffer[i][currentSignalIndex[i]] = newSignal;
   
    //if new signal is 0
    if(newSignal < thresholdMap[i])
    {
      if(!isLastPeakZeroed[i] && (currentTime - lastPeakTime[i]) > MAX_TIME_BETWEEN_PEAKS)
      {
        recordNewPeak(i,0);
      }
      else
      {
        //get previous signal
        short prevSignalIndex = currentSignalIndex[i]-1;
        if(prevSignalIndex < 0) prevSignalIndex = SIGNAL_BUFFER_SIZE-1;       
        unsigned short prevSignal = signalBuffer[i][prevSignalIndex];
       
        unsigned short newPeak = 0;
       
        //find the wave peak if previous signal was not 0 by going
        //through previous signal values until another 0 is reached
        while(prevSignal >= thresholdMap[i])
        {
          if(signalBuffer[i][prevSignalIndex] > newPeak)
          {
            newPeak = signalBuffer[i][prevSignalIndex];       
          }
         
          //decrement previous signal index, and get previous signal
          prevSignalIndex--;
          if(prevSignalIndex < 0) prevSignalIndex = SIGNAL_BUFFER_SIZE-1;
          prevSignal = signalBuffer[i][prevSignalIndex];
        }
       
        if(newPeak > 0)
        {
          recordNewPeak(i, newPeak);
        }
      }
 
    }
       
    currentSignalIndex[i]++;
    if(currentSignalIndex[i] == SIGNAL_BUFFER_SIZE) currentSignalIndex[i] = 0;
  }
}

void recordNewPeak(short slot, short newPeak)
{
  isLastPeakZeroed[slot] = (newPeak == 0);
 
  unsigned long currentTime = millis();
  lastPeakTime[slot] = currentTime;
 
  //new peak recorded (newPeak)
  peakBuffer[slot][currentPeakIndex[slot]] = newPeak;
 
  //1 of 3 cases can happen:
  // 1) note ready - if new peak >= previous peak
  // 2) note fire - if new peak < previous peak and previous peak was a note ready
  // 3) no note - if new peak < previous peak and previous peak was NOT note ready
 
  //get previous peak
  short prevPeakIndex = currentPeakIndex[slot]-1;
  if(prevPeakIndex < 0) prevPeakIndex = PEAK_BUFFER_SIZE-1;       
  unsigned short prevPeak = peakBuffer[slot][prevPeakIndex];
   
  if(newPeak > prevPeak && (currentTime - lastNoteTime[slot])>MIN_TIME_BETWEEN_NOTES)
  {
    noteReady[slot] = true;
    if(newPeak > noteReadyVelocity[slot])
      noteReadyVelocity[slot] = newPeak;
  }
  else if(newPeak < prevPeak && noteReady[slot])
  {
    noteFire(noteMap[slot], noteReadyVelocity[slot]);
    noteReady[slot] = false;
    noteReadyVelocity[slot] = 0;
    lastNoteTime[slot] = currentTime;
  }
 
  currentPeakIndex[slot]++;
  if(currentPeakIndex[slot] == PEAK_BUFFER_SIZE) currentPeakIndex[slot] = 0; 
}

void noteFire(unsigned short note, unsigned short velocity)
{
  if(velocity > MAX_MIDI_VELOCITY)
    velocity = MAX_MIDI_VELOCITY;
 
  midiNoteOn(note, velocity);
  midiNoteOff(note, velocity);
}

void midiNoteOn(byte note, byte midiVelocity)
{
  Serial.write(NOTE_ON_CMD);
  Serial.write(note);
  Serial.write(midiVelocity);
}

void midiNoteOff(byte note, byte midiVelocity)
{
  Serial.write(NOTE_OFF_CMD);
  Serial.write(note);
  Serial.write(midiVelocity);
}



As i've only had an arduino for about two weeks and with no prior experince with code, I have absolutly no idea how this works. All i know is that overly hyperactive asian is now my favorite kind of person.

Heres the video I'm talking about. it's worth checking this guy out, it looks like he has done tons of neat things with MIDI and arduino and small electronics.
https://www.youtube.com/watch?v=vi-w_WqJjzQ

But for now my Leonardo has completly crapped out on me  :smiley-confuse:  sooooooo im running the UNO with hairless and heading to the correct forum to figure out this error message.

Grumpy_Mike

Quote
and does not use the MIDI cereal library
That made me laugh.

This is a cereal


This is a serial port


nickevasion

Good haha it's a South Park joke from the man bear pig episode I did it on purpose

Grumpy_Mike


Go Up