Arduino Midi Drum sending Multiple Midi messages in a Fraction of seconds

Hello guys! I hope you are doing good!
So, after a long while, I have started messing with arduino again.
But this time I have accomplished good things!
I’ve written myself a midi controller code from scratch, and have learned the basics and even some advanced stuff!
There’s nuch to learn yet, and that’s why I am kinda lost in this new task.
I want to read a piezo value so I can build myself a midi drum.
I’ve started researching and discovered that this code does all that I want!

#include <MIDI.h>
MIDI_CREATE_DEFAULT_INSTANCE();

byte triggerThreshold[] = {30, 30}; // If this is set too low, hits on other pads will trigger a "hit" on this pad
byte initialHitReadDuration[] = {500, 500}; // In microseconds. Shorter times will mean less latency, but less accuracy. 500 microseconds is nothing, anyway
byte midiVelocityScaleDownAmount[] = {4, 3}; // Number of halvings that will be applied to MIDI velocity
byte inputPin[] = {A0, A1};
byte midiNote[] = {38, 35};
 
// Getting the ideal balance of these two constants will ensure that fast subsequent hits are perceived accurately, but false hits are not generated
byte subsequentHitThreshold[] = {1.7, 1.7};
byte subsequentHitThresholdDecaySpeed[] = {14, 14};
 
uint16_t highestYet[2];
uint32_t startReadingTime[2];
uint32_t highestvalueTime[2];
boolean hitOccurredRecently[2] = {false, false};
 
void setup() {
  Serial.begin(9600);
}
 
void loop() {
       for (byte i = 0; i < 2; i ++) {
  // Assume the normal hit-threshold
  uint16_t thresholdNow = triggerThreshold[i];
 
  // But, if a hit occurred very recently, we need to set a higher threshold for triggering another hit, otherwise the dissipating vibrations
  // of the previous hit would trigger another one now
  if (hitOccurredRecently[i]) {
 
      // Work out how high a reading we'd need to see right now in order to conclude that another hit has occurred
      uint16_t currentDynamicThreshold = (highestYet[i] >> ((micros() - highestvalueTime[i]) >> subsequentHitThresholdDecaySpeed[i])) * subsequentHitThreshold[i];
 
      // If that calculated threshold is now as low as the regular threshold, we can go back to just waiting for a regular, isolated hit
      if (currentDynamicThreshold <= triggerThreshold[i]) hitOccurredRecently[i] = false;
 
      // Otherwise, do use this higher threshold
      else thresholdNow = currentDynamicThreshold;
  }
 
  // Read the piezo


  uint16_t value[2] = {analogRead(inputPin[i]),analogRead(inputPin[i])};
 
  // If we've breached the threshold, it means we've got a hit!
  if (value[i] >= thresholdNow) {
    startReadingTime[i] = micros();
    highestYet[i] = 0;
 
    // For the next few milliseconds, look out for the highest "spike" in the reading from the piezo. Its height is representative of the hit's velocity
    do {
      if (value[i] > highestYet[i]) {
        highestYet[i] = value[i];
        highestvalueTime[i] = micros();
      }
      value[i] = analogRead(inputPin[i]);
    } while (timeGreaterOrEqual(startReadingTime[i] + initialHitReadDuration[i], micros()));
 
    // Send the MIDI note
    MIDI.sendNoteOn(midiNote[i], (highestYet[i] >> midiVelocityScaleDownAmount[i]) + 1, 1); // We add 1 onto the velocity so that the result is never 0, which would mean the same as a note-off
    //Serial.println(highestYet); // Send the unscaled velocity value[i] to the serial monitor too, for debugging / fine-tuning
    hitOccurredRecently[i] = true;
  }
}
}
 
// Compares times without being prone to problems when the micros() counter overflows, every ~70 mins
boolean timeGreaterOrEqual(uint32_t lhs, uint32_t rhs) {
  return (((lhs - rhs) & 2147483648) == 0);
}

The problem is that it sends at least 3 midi messages almost at the same time , and the same note…
So let us say that I am hitting the snare Pad… It will sound almost as it was hit just once, but it will send 3 or more midi messages for the same note… (you can see the attachment for a clearer explanation).
While this is ok for playing along, it can be quite annoying for a mix, where you will have to delete every double(or triple) note…

I have tried several modifications, but none of them improved.
I suspect that this is the problematc line:

while (timeGreaterOrEqual(startReadingTime[i] + initialHitReadDuration[i], micros()));
 
    // Send the MIDI note
    MIDI.sendNoteOn(midiNote[i], (highestYet[i] >> midiVelocityScaleDownAmount[i]) + 1, 1); // We add 1 onto the velocity so that the result is never 0, which would mean the same as a note-off
    //Serial.println(highestYet); // Send the unscaled velocity value[i] to the serial monitor too, for debugging / fine-tuning
    hitOccurredRecently[i] = true;

I have tried creating an if else statement and making the boolean false, but it makes things worse…
I don’t know what else to do!

The original code is here:
Drum Code

I have changed just a couple of things: As this was mainly written for a Teensy, and I am using an Arduino Mega…

Also, I have created some arrays to store more than one pad and etc, nothing fancy. But this will happen with only one pad as well, so it’s not crosstalk problem.
I have tried using higher resistance resistors, but the result is the same.
I have used a diode, and nothing changes.
I have tried comparing it to other drum codes, but as the were really different, I did not get a clue.

I’d like to use this code rather than any other, because this one has a really good calculation of the hit vs velocity!

Any help is highly appreciated!
Thanks in advance!

Cheers :slight_smile:

A couple of comments:

byte subsequentHitThreshold[] = {1.7, 1.7};

You can't store floating point numbers in a 'byte' variable. These get truncated to 1 which means

      uint16_t currentDynamicThreshold = (highestYet[i] >> ((micros() - highestvalueTime[i]) >> subsequentHitThresholdDecaySpeed[i])) * subsequentHitThreshold[i];

you are always multiplying by 1. Also, are you sure you want to be right shifting bits using time values? This seems very odd.

Your variable 'value' which hold the piezo readings could be declared at the beginning of the loop() function and then you just need to read the value of the analog input for 'i'

    // Read the piezo
    uint16_t value[2];
    value[i]= analogRead(inputPin[i]);

Lastly, you should only ever subtract time values (millis() or micros()) to make sure they work correctly, even with rollover. [currentTime - startTime >= duration]. This applies to your timeGreaterOrEqual() function

Piezos generate an AC voltage when you hit them. This means that you have multiple peaks in fast succession.
It also means that you are stressing your Arduino, as it is not designed for reading negative voltages.

Here's a capture of a piezo disk being hit. Note that the behavior depends heavily on how it is mounted and how it is hit. It will also depend on how the piezo is loaded, whether it is clamped using a diode, etc.

You need a peak detector or envelope follower. If you add a series resistor, the clamping diode of the Arduino can get rid of the negative parts of the signal, allowing you to do peak detection in software, rather than hardware.

Pieter

Hey blh64! Thanks for the answer!
I thought that nobody was going to answer, so I kept fighting and searching.
I ended up finding a “simpler” code, yet, powerful!
I have made some changes in it because this one also was meant to be used with a Teensy.

Here is the new code:

#include <MIDI.h>
#include <elapsedMillis.h>

MIDI_CREATE_DEFAULT_INSTANCE();

const int channel = 1;  // General MIDI: channel 10 = percussion sounds
const int PINS = 2;     // number of signals incoming
const int amt[PINS] = {1.5, 1.5};
const int note[PINS] = {38, 35};    // array of MIDI note values for read signals
const int analogPin[PINS] = {A0, A1}; //array of analog PINs
const int thresholdMin[PINS] = {30, 30};  // minimum reading, avoid noise and false starts
const int peakTrackMillis = 12;
const int aftershockMillis = 25; // aftershocks & vibration reject

int state[PINS];  // 0=idle, 1=looking for peak, 2=ignore aftershocks
int peak[PINS];   // remember the highest reading
int piezo[PINS];
elapsedMillis msec[PINS]; // timers to end states 1 and 2

void setup() {
  Serial.begin(9600);
}


void loop() {
  for (int i = 0; i < PINS; i++) {
    //delay(20);
    piezo[i] = analogRead(analogPin[i]);

    peakDetect(i);
    // Add other tasks to loop, but avoid using delay() or waiting.
    // You need loop() to keep running rapidly to detect Piezo peaks!

  }
  // MIDI Controllers should discard incoming MIDI messages.
  // http://forum.pjrc.com/threads/24179-Teensy-3-Ableton-Analog-CC-causes-midi-crash
  while (MIDI.read()) {
    // ignore incoming messages
  }
}


void peakDetect(int i) {

  //Serial.println(state[i]);

  switch (state[i]) {
    // IDLE state: wait for any reading is above threshold.  Do not set
    // the threshold too low.  You don't want to be too sensitive to slight
    // vibration.
    case 0:
      if (piezo[i] > thresholdMin[i]) {
        //Serial.print("begin peak track ");
        //Serial.println(piezo[i]);
        peak[i] = piezo[i];
        msec[i] = 0;
        state[i] = 1;
      }
      return;

    // Peak Tracking state: capture largest reading
    case 1:
      if (piezo[i] > peak[i]) {
        peak[i] = piezo[i];
      }
      if (msec[i] >= peakTrackMillis) {
        //Serial.print("peak = ");
        //Serial.println(peak);
        int velocity = map(peak[i], thresholdMin[i], 1023, 1, 127);
        MIDI.sendNoteOn(note[i], (velocity >> amt[i]), channel);
        msec[i] = 0;
        state[i] = 2;
      }
      return;

    // Ignore Aftershock state: wait for things to be quiet again.
    default:
      if (piezo[i] > thresholdMin[i]) {
        msec[i] = 0; // keep resetting timer if above threshold
      } else if (msec[i] > aftershockMillis) {
        MIDI.sendNoteOff(note[i], 0, channel);
        state[i] = 0; // go back to idle when
      }
  }
}

This code works better than the other and is easier for a beginer like me to understand some things.

I honestly don’t know why use “right shifting bits using time values”… This is the problem using someone else’s code! haha

But with this new code I am using right shifting in this part here:

MIDI.sendNoteOn(note_, (velocity >> amt*), channel);[/b]
But here, I am using because if I use “velocity” alone, it seems to be TOO sensitive.
For instance, if I apply a force of 5 in the hit, it will send a note with velocity of 5.
But with a 15 force, it will send a note with 127 velocity.
Whereas when usign this right shifting, things scale smoother!
But the first thing I went to look in this new code was the byte/float thing.
and when I try to compile the code with :
const float amt[PINS] = {1.5, 1.5};
Instead of:
const int amt[PINS] = {1.5, 1.5};
I’ll have this error: invalid operands of types ‘int’ and ‘const float’ to binary ‘operator>>’
And if I change:
intvelocity = map(peak, thresholdMin, 1023, 1, 12[/b]
to
float velocity = map(peak, thresholdMin, 1023, 1, 12[/b]*

I have this: invalid operands of types ‘float’ and ‘const float’ to binary ‘operator>>’
Can’t float be used for right shifting?
I kinda understand it because, as far as I know, right shifting works somewhat like this:
00000010000
if I right shift 1 by 2
00000000100
and so on…
But what if amt = 1 is too sensitive and amt = 2 is not ? what If I want to be in the middle ?
I think this code is far easier to understand.
*But this “smoothing” part is bugging me! *
Hey PieterP! Thanks for the answer as well.
The diode is not needed neither with the 1st code nor with this second one I am posting (at least that’s what is told where I’ve read. But if you say it is needed, I wont bother buyng some diodes and putting them on the project!)
But I am using a resistor, yes.
This last code is easier to understand, to maintain, and it also doesn’t send a gazillion notes per hit.
So, I am going somewhere!_

Erikson:
const int amt[PINS] = {1.5, 1.5};

same problem. This is EXACTLY the same as
const int amt[PINS] = {1, 1};

Can't float be used for right shifting?

Nope. You are moving bit an integer number of positions, not fractional

But is there another way that I can smooth things ?
Because The intensity is varying way too much with 1 but much to low at 2...

Erikson:
But is there another way that I can smooth things ?
Because The intensity is varying way too much with 1 but much to low at 2...

Yes, but you have to do floating point math rather than using a shift operator

Is there any link where I can read and understand how to do that ?
I was thinking about another option as well...
The max velocity in midi is 127 and the max analog read of the piezo is 1023.
The math that people do is that velocity = analogRead / 8 .
What if instead of dividing it, using right shifting?
If that even makes sense... haha

It does make sense. Right shifting a value 1 bit is the same as dividing by 2 so right shifting a value by 3 bits is the same as dividing by 8 (2^3 = 8)

Ok!
I am trying here!
but can you also give me a clue on how I can do the maths to go from 0 to 127 as smooth as possible using float?
Thank you so much!

I think that you are going to have to choose between

smooth and float and multiply (or divide) and somewhat slow

versus

coarse and int (or byte) and bit shift and fast.

Will using float significantly slow the code?
I can try both...
Right now, bit shifting has been cool, but if it can be improved, why not?

Guys, I think my problem is not the dividing, multiplying or floating...
My problem maybe is not possible to solve, but I will try to explain...

I want to need a harder beat to achieve the maximum possible value, which is 127.
In a real drum, its not hard to achieve the louder value, but is not so easy!
If I , for instance, just let the stick "fall" in the pad, it will have a 127 value...
It's not THAT bad, but sometimes its hard to hold the hand soft 100% of the time...
I think it doesn't have to do with the dividing, but...
I don't know if I am clear , but is there a way to achieve that ?
Thanks and sorry for that many questions!