Help with Piezo Triggers --> MIDI over USB [Solved]

Hi all, I'm really glad I found these forums, they have been an invaluable resource! I have a question that someone might be able to help with if they have a bit of familiarity with C++ ...I am still only starting to get my head around it.

I've used this example by Ian Harvey as a starting point to create a MIDI hand-drum. The idea is piezo triggers send velocity sensitive 'hits' to MIDI over USB. There are two pots to control scale and key.

The issue I'm having is when I connect to my Mac running Logic, there are low velocity hits running constantly in the background. I'm not sure where they are coming from in the code. When I strike the piezos they sometimes work to strike a note and sometimes do not work. I've tried adjusting the sensitivity parameters up and down, but it does not seem to have much effect.

In addition, in some scales the controller seems to send short staccato notes, sometimes long sustained notes. I haven't worked out why this is.

Can anyone see at a glance where the background signals are coming from, and what's causing the discrepancy in note length? I have to admit I don't quite understand the MIDI_noteOff function and the various timers. I'm unreasonably pleased for just getting the pots to work for setting scales!

Any help gratefully received.

]

#include "MIDIUSB.h"
// Needs MIDIUSB library to be installed

static void MIDI_setup();
static void MIDI_noteOn(int ch, int note, int velocity);
static void MIDI_noteOff(int ch, int note);

const int MIDI_CHANNEL=1;
const int BAUD_RATE=115200;

const int scalePin = A0;
const int keyPin = A1;
const int NCHANNELS = 3;
const int KEYRANGE = 12; // Range in semitones
const int NSCALES = 7; // Number of modes/scales
const int BASENOTE = 50; // Lowest available note (MIDI number)
const int inPins[NCHANNELS] = { A2, A3};
const int thresholdLevel[NCHANNELS] = { 50, 50 }; // ADC reading to trigger; lower => more sensitive
const int maxLevel[NCHANNELS] = { 200, 200 }; // ADC reading for full velocity; lower => more sensitive
const int SCALES[NSCALES][9] = {
  {0,2,4,5,7,9,11,12,14}, // Ionian, Major
  {0,4,7,11,12,16,18,19,23}, // Aegean
  {0,3,7,10,12,14,15,19,21}, // Voyager
  {0,3,7,8,10,13,15,16,20}, // Equinox
  {0,5,7,8,10,12,14,15,17}, // Dorian
  {0,7,8,10,12,14,15,19,21}, // Phrygian
  {0,5,7,8,12,13,17,19,20}, // Ake Bono 
};

int midiNotes[NCHANNELS];
int scaleCounter;
int key;
int scale;

static unsigned int vmax[NCHANNELS] = { 0 };
static unsigned int trigLevel[NCHANNELS];
static unsigned int counter[NCHANNELS] = { 0 };

static unsigned int CTR_NOTEON = 10; // Roughly 5ms sampling peak voltage
static unsigned int CTR_NOTEOFF = CTR_NOTEON + 30; // Duration roughly 15ms 
// 0 -> not triggered
// 1..CTR_NOTEON -> sampling note on
// CTR_NOTEON+1 .. CTR_NOTEOFF -> note off


static int statusPin = 2;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(BAUD_RATE);
  analogReference(DEFAULT);
  pinMode(statusPin, OUTPUT);
  digitalWrite(statusPin, LOW);
  
  for (int i = 0; i < NCHANNELS; i++)
  {
    pinMode(inPins[i], INPUT);
    analogRead(inPins[i]);
    trigLevel[i] = thresholdLevel[i];
  }

  MIDI_setup();
}

void loop() 
{    
  // volumeCheck();//check the volume knob 

  drumCheck();//check if any of the capacitive pads have been touched

  // dcValCheck();//check the decay knob

  // buttonCheck();//check for button presses to change the scale 

  scaleCounter++;
  
  if(scaleCounter == 100);//check scale and key periodically
  {
     scaleCheck();
     scaleCounter = 0;
  }

  // counter++;
  // if(counter == 10000);//don't check battery all the time, slows opperation
  // {
  //   getBattery();
  //   counter = 0;
  // }
  
  // oledPrint();//print to TeensyView

}

void scaleCheck() {
  int sv = analogRead(scalePin);
  int s = map(sv, 0, 1023, 0, NSCALES);
  int kv = analogRead(keyPin);
  int k = map(kv, 0, 1023, 0, KEYRANGE);
  if ( s != scale || k != key )
  {
    int n;
    for (n=0; n < NCHANNELS; n++)
    {
      midiNotes[n] = BASENOTE + k + SCALES[s][n];
    }
    s = scale;
    k = key;
  }
}


void drumCheck() {
  int ch;
  for (ch=0; ch < NCHANNELS; ch++)
  {
    unsigned int v = analogRead(inPins[ch]);
    if ( counter[ch] == 0 )
    {
      if ( v >= trigLevel[ch] )
      {
        vmax[ch] = v;
        counter[ch] = 1;
        digitalWrite(statusPin, HIGH);
      }
    }
    else
    {
      if ( v > vmax[ch] )
        vmax[ch] = v;
      counter[ch]++;
      
      if ( counter[ch] == CTR_NOTEON )
      {
        long int vel = ((long int)vmax[ch]*127)/maxLevel[ch];
        //Serial.println(vel);
        if (vel < 5) vel = 5;
        if (vel > 127) vel = 127;
        MIDI_noteOn(MIDI_CHANNEL, midiNotes[ch], vel);
        trigLevel[ch] = vmax[ch];
      }
      else if ( counter[ch] >= CTR_NOTEOFF )
      {
        MIDI_noteOff(MIDI_CHANNEL, midiNotes[ch]);
        counter[ch] = 0;
        digitalWrite(statusPin, LOW);
      }
    }

    // The signal from the piezo is a damped oscillation decaying with
    // time constant 8-10ms. Prevent false retriggering by raising 
    // trigger level when first triggered, then decaying it to the 
    // threshold over several future samples.
    trigLevel[ch] = ((trigLevel[ch] * 29) + (thresholdLevel[ch] * 1)) / 30;
  }

}

// MIDI Code
//
// See https://www.midi.org/specifications/item/table-1-summary-of-midi-message

void MIDI_setup()
{

}

void MIDI_noteOn(int ch, int note, int velocity)
{
  midiEventPacket_t noteOn = {0x09, 0x90 | (ch-1), note & 0x7F, velocity & 0x7F};
  MidiUSB.sendMIDI(noteOn);
  MidiUSB.flush();
}

void MIDI_noteOff(int ch, int note)
{
  midiEventPacket_t noteOff = {0x08, 0x80 | (ch-1), note, 1};
  MidiUSB.sendMIDI(noteOff);
  MidiUSB.flush();
}

For one thing, this is wrong

  if (scaleCounter == 100); //check scale and key periodically
  {
    scaleCheck();
    scaleCounter = 0;
  }

You call scaleCheck() every time through loop because you have a trailing ';' after your if() which terminates the if() block, making the code you think is associated with the if() actual run every time.

According to the comments, you can also increase these values to make it less sensitive. Have you tried that?

const int thresholdLevel[NCHANNELS] = { 50, 50 }; // ADC reading to trigger; lower => more sensitive

Thank you very much for catching that, I'll fix it now! I have tried adjusting the sensitivity which seems to have no effect. I might try looking around for more sample code and try some different implementations.

millipz:
Thank you very much for catching that, I'll fix it now! I have tried adjusting the sensitivity which seems to have no effect. I might try looking around for more sample code and try some different implementations.

and what values have you tried? Those values represent the lowest ADC reading that is noise (I think, based on a quick glance at the code) so did you crank them way up to 512 (1/2 the ADC range)? or higher?

Thank you! I've found the issue, I was declaring 3 channels but only assigning two pins, so there was an extra channel floating around creating low level background noise. I'll keep working on this to add a multiplexer, and share my code once it's all working.

Cheers