Latency on piezo + teensy (drums)

Status
Not open for further replies.

musiquemeuble

Active member
Hello,

I'm building an electronic drum kit with Teensy + piezo + Ableton live. It's quite working well, but the main problem is latency (enough to disturb the play) ..

Here is my code

Code:
/*
  MIDI Toy Piano Hack Code
*/

#include <MIDI.h>
MIDI_CREATE_DEFAULT_INSTANCE();

//========================================================================================
//Values you may need to change

//Set this value to the number of keys/piezos you are using
const int NUM_OF_KEYS = 16;
//Adjust this value to change the sensitivity of the piezos
const int THRESHOLD = 10;
//Set this value to the number of microseconds you want each MIDI note to last for
const int NOTE_LENGTH = 3;
//Adjust this value to set the range of MIDI note velocity values
const int VEL_SENSE_VAL = 2;

//Change this number to set what MIDI channel the MIDI notes are set to
const int midiChan = 1;
//Change these numbers to set what MIDI note number each key/piezo will send.
//Also make sure that the total number of numbers here matches the value of NUM_OF_KEYS
const int midiNote[NUM_OF_KEYS] = {86};

//Change these values to set which analog input pins you are using
//Also make sure that the total number of values here matches the value of NUM_OF_KEYS
const int triggerSensor[NUM_OF_KEYS] = {A0};

//=======================================================================================

//Variables for storing certain values
int triggerVal[NUM_OF_KEYS] = {0};
bool noteIsOn[NUM_OF_KEYS] = {false};
int midiNoteTime[NUM_OF_KEYS] = {0};
int midiVelocityVal[NUM_OF_KEYS] = {0};

void setup()
{
  MIDI.begin(MIDI_CHANNEL_OMNI);
  Serial.begin(9600); //REMOVE THIS LINE IF USING HIDUINO INSTEAD OF HAIRLESS
}

void loop()
{
  //repeat the below code for each anaolog input/piezo sensor
  for (int count; count < NUM_OF_KEYS; count++)
  {
    //read the piezo value
    triggerVal[count] = analogRead(triggerSensor[count]);

    //if the value is over the threshold and there isn't currently a note on for this piezo
    if (triggerVal[count] > THRESHOLD && noteIsOn[count] == false)
    {
      //get a velocity value based on the value
      midiVelocityVal[count] = triggerVal[count] * (127.0 / 1023.0);

      //increase sensitivity
      midiVelocityVal[count] *= VEL_SENSE_VAL;

      //make sure we don't go out of range
      if (midiVelocityVal[count] > 127)
        midiVelocityVal[count] = 127;

      //send a MIDI note-on message
      MIDI.sendNoteOn (midiNote[count], midiVelocityVal[count], midiChan);

      //flag that the note is on
      noteIsOn[count] = true;

      //start a timer for the note to be on for
      midiNoteTime[count] = NOTE_LENGTH;
    }

    //if the note is currently on
    if (noteIsOn[count] == true)
    {
      //reduce the time value by 1
      midiNoteTime[count]--;

      //if time value equals 0
      if (midiNoteTime[count] == 0)
      {
        //turn off the note
        MIDI.sendNoteOff (midiNote[count], 0, midiChan);
        noteIsOn[count] = false;
      }

    }

  }

  //pause the loop
  delay(1);
}

What do you think ? Do you have a solution ? I'm on a Macbook pro.

Thanks
Florent
 
This part looks like a problem.

Code:
//Change these values to set which analog input pins you are using
//Also make sure that the total number of values here matches the value of NUM_OF_KEYS
const int triggerSensor[NUM_OF_KEYS] = {A0};

The array is 16 elements, but only the first is initialized. Likewise for the other arrays...
 
I ran it just now on a Teensy 3.6. I connected a 10K resistor from 14/A0 to GND.

First, the threshold is too low. It transmits a continuous stream of data.

With the threshold increased to 50, here is what I see on pin 1 (Serial1 TX) when I touch a wire between 14/A0 and 3.3V.

file.png

The latency from the moment the voltage at A0/14 goes high until the first start bit of the MIDI message looks like about 400 us (not quite a 1 division). Looks very fast to me on Teensy 3.6. Even if it becomes 16X slower when using all 16 inputs, the speed is very good.

However, there's another possible problem. This code (did you write it?) appears to continue sending messages repeated if the signal stays above the threashold. Here's what I see on the scope if I zoom out to view 0.2 seconds.

file.png

Here's how I tested...

DSC_0128_web.jpg
 
There is plenty wrong with this code... apart from you not understanding the array assignments.

It doesn't use Teensy's built in MIDI but from the comments looks like it's meant to work as a USB MIDI device using Arduinio kludgeware. ;)

Ok that's unfair but Paul's USB MIDI is the way to go on Teensy if you're not building a 5-pin DIN interface.

With that you don't need to include the MIDI.h and you can use serial for printing to the serial monitor while debugging.


I'm surprised latency is the problem though as the code itself slows down on whatever it's run on.

This code delays every pass... that means it's like running on a 1000 Hz clocked computer. So even though your T3.6 is relative supercomputer to what this code would usually run on, its speed is all wasted.

Paul's example code for a single Piezo trigger (File-Examples-Teensy-USB_MIDI-Piezo_Drum) is a good place to start for a single trigger.

There's a thread here somewhere on someone trying to generalize it to multiple triggers... I can't recall how far they got... I'll post the link when I locate it...

(@ Paul... did you remove the delay(1) to get that... the code relies on the delay for timing.)
 
I found the thread but it goes on for pages about other issues...

My post HERE does have some code that might be a better starting place. It is essentially a multi-pin extension of Paul's Piezo trigger code.

Code:
/* Use a Piezo sensor (percussion / drum) to send USB MIDI note on
   messages, where the "velocity" represents how hard the Piezo was
   tapped.

   Connect a Pieze sensor to analog pin A0.  This example was tested
   with Murata 7BB-27-4L0.  Almost any piezo sensor (not a buzzer with
   built-in oscillator electronics) may be used.  However, Piezo
   sensors are easily damaged by excessive heat if soldering.  It
   is highly recommended to buy a Piezo with wires already attached!

   Use a 100K resistor between A0 to GND, to give the sensor a "load".
   The value of this resistor determines how "sensitive" the circuit is.

   A pair of 1N4148 diodes are recommended to protect the analog pin.
   The first diode connects to A0 with its stripe (cathode) and the other
   side to GND.  The other diode connects its non-stripe (anode) side to
   A0, and its stripe (cathode) side to 3.3V.

   Sensitivity may also be tuned with the map() function.  Uncomment
   the Serial.print lines to see the actual analog measurements in the
   Arduino Serial Monitor.

   You must select MIDI from the "Tools > USB Type" menu

   This example code is in the public domain.
   *multi-pad extension by oddson (under-tested)*
*/

const int channel = 10;  // General MIDI: channel 10 = percussion sounds
const int PINS = 3;     // number of signals incoming

const int note[PINS] = {36,37,38};     // array of MIDI note values for read signals

const int analogPin[PINS] = {A0,A1,A3}; //array of analog PINs 
const int thresholdMin = 60;  // 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(115200);
  while (!Serial && millis() < 2500) /* wait for serial monitor */ ;
  Serial.println("Piezo Peak Capture");
}


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 (usbMIDI.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) {
        //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, 1023, 1, 127);
        usbMIDI.sendNoteOn(note[i], velocity, channel);
        msec[i] = 0;
        state[i] = 2;
      }
      return;

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

Thanks for the answers. I did'nt write this code, I took it from someone and then change it. I'm really newbie.. @paul thanks for testing it so quickly ! @oddson thanks for the code I'll try it !!
 
When you choose MIDI as the USB type to compile code it will appear as a class compliant MIDI device.

There is example code that shows one way to capture piezo signals and output MIDI note events with a velocity value proportional to the signal peak.

This code attempts to extend that code for multiple sensors. It appears to work but I have not tested it personally.

Make sure you protect Teensy from voltage spikes. Read the comment at te top carried over from the example code.
 
Thanks a lot

There is example code that shows one way to capture piezo signals and output MIDI note events with a velocity value proportional to the signal peak.

I can't find it, is it in the Teensy exemples library ?
 
Paul's example code for a single Piezo trigger (File-Examples-Teensy-USB_MIDI-Piezo_Drum) is a good place to start for a single trigger.
that's the path in paranthesthes
 
hi everybody, I come to midi with piezo for the first time, I just tried the code that odds-on gave my teensy 4.0 seems to always send 127 for velocity, I wired my piezo to A0 and A1 with 1M resistor and also tried with 100k and it s the same, I don t have the diode here, may this be my problem or can I check something else please?


I use this piezo https://fr.farnell.com/multicomp/abt-441-rc/piezo-27mm-4200hz-cablee/dp/1675548?CMP=i-bf9f-00001000


Code:
/* Use a Piezo sensor (percussion / drum) to send USB MIDI note on
   messages, where the "velocity" represents how hard the Piezo was
   tapped.

   Connect a Pieze sensor to analog pin A0.  This example was tested
   with Murata 7BB-27-4L0.  Almost any piezo sensor (not a buzzer with
   built-in oscillator electronics) may be used.  However, Piezo
   sensors are easily damaged by excessive heat if soldering.  It
   is highly recommended to buy a Piezo with wires already attached!

   Use a 100K resistor between A0 to GND, to give the sensor a "load".
   The value of this resistor determines how "sensitive" the circuit is.

   A pair of 1N4148 diodes are recommended to protect the analog pin.
   The first diode connects to A0 with its stripe (cathode) and the other
   side to GND.  The other diode connects its non-stripe (anode) side to
   A0, and its stripe (cathode) side to 3.3V.

   Sensitivity may also be tuned with the map() function.  Uncomment
   the Serial.print lines to see the actual analog measurements in the
   Arduino Serial Monitor.

   You must select MIDI from the "Tools > USB Type" menu

   This example code is in the public domain.
   *multi-pad extension by oddson (under-tested)*
*/

const int channel = 10;  // General MIDI: channel 10 = percussion sounds
const int PINS = 2;     // number of signals incoming

const int note[PINS] = {36,37};     // array of MIDI note values for read signals

const int analogPin[PINS] = {A0,A1}; //array of analog PINs 
const int thresholdMin = 60;  // 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(115200);
  while (!Serial && millis() < 2500) /* wait for serial monitor */ ;
  Serial.println("Piezo Peak Capture");
}


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 (usbMIDI.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) {
        //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, 1023, 1, 127);
        Serial.println (velocity);
        usbMIDI.sendNoteOn(note[i], velocity, channel);
        msec[i] = 0;
        state[i] = 2;
      }
      return;

    // Ignore Aftershock state: wait for things to be quiet again.
    default:
      if (piezo[i] > thresholdMin) {
        msec[i] = 0; // keep resetting timer if above threshold
      } else if (msec[i] > aftershockMillis) {
        usbMIDI.sendNoteOff(note[i], 0, channel);
        state[i] = 0; // go back to idle when
      }
  }
}
 
That means in Arduino, you click File > Examples > Teensy > USB_MIDI > Piezo_Drum
hi, now I can get values of 354 or 738 or 1023 in serial monitor but the velocity sent is always at 127
anyone got idea how I could make this drummed working with velocity please?
 
hi, now I can get values of 354 or 738 or 1023 in serial monitor but the velocity sent is always at 127
anyone got idea how I could make this drummed working with velocity please?

Code:
        int velocity = map(peak[i], thresholdMin, 1023, 1, 127);
        Serial.println (velocity);
        usbMIDI.sendNoteOn(note[i], velocity, channel);
Yeah, how can you be getting those values at all? If you are then the MIDI likely would max out at 127 (I've never tried to overload it or ever bothered to remember the result when I did). But how can println(velocity) give numbers that haven't been mapped from the raw values?

I've NEVER run this code on anything resembling piezo hardware. I only extended it to arrays for multiple signals so my 'expertise' is suspect but the behaviour as you describe it doesn't seem consistent with the above posted code.

Can you confirm you're referring to that code?
 
hi, thanks for answering! the values I got are voltage not velocity, I was able to receive 354 or 738 or 1023 as voltage but the values sent always the max velocity, what I d like to achieve is using 2 piezo for the moment that send different velocity over usb I m a beginner in coding I can t make work the code you gave I m experimenting around it but can t find why I always receive the same voltage
with that code my piezo always print 1023 on the serial monitor


Code:
/* Use a Piezo sensor (percussion / drum) to send USB MIDI note on
   messages, where the "velocity" represents how hard the Piezo was
   tapped.

   Connect a Pieze sensor to analog pin A0.  This example was tested
   with Murata 7BB-27-4L0.  Almost any piezo sensor (not a buzzer with
   built-in oscillator electronics) may be used.  However, Piezo
   sensors are easily damaged by excessive heat if soldering.  It
   is highly recommended to buy a Piezo with wires already attached!

   Use a 100K resistor between A0 to GND, to give the sensor a "load".
   The value of this resistor determines how "sensitive" the circuit is.

   A pair of 1N4148 diodes are recommended to protect the analog pin.
   The first diode connects to A0 with its stripe (cathode) and the other
   side to GND.  The other diode connects its non-stripe (anode) side to
   A0, and its stripe (cathode) side to 3.3V.

   Sensitivity may also be tuned with the map() function.  Uncomment
   the Serial.print lines to see the actual analog measurements in the
   Arduino Serial Monitor.

   You must select MIDI from the "Tools > USB Type" menu

   This example code is in the public domain.
   *multi-pad extension by oddson (under-tested)*
*/

const int channel = 10;  // General MIDI: channel 10 = percussion sounds
const int PINS = 2;     // number of signals incoming

const int note[PINS] = {36,37};     // array of MIDI note values for read signals

const int analogPin[PINS] = {A0,A1}; //array of analog PINs 
const int thresholdMin = 1;  // minimum reading, avoid noise and false starts
const int peakTrackMillis = 200;
const int aftershockMillis = 60; // 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(115200);
  while (!Serial && millis() < 2500) /* wait for serial monitor */ ;
  Serial.println("Piezo Peak Capture");
}


void loop() {
  for (int i=0;i<PINS;i++){
    delay(5);
    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 (usbMIDI.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) {
        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[i]);
        int velocity = map(piezo[i], thresholdMin, 1023, 80, 127);
        usbMIDI.sendNoteOn(note[i], velocity, channel);
        msec[i] = 0;
        state[i] = 2;
      }
      return;

    // Ignore Aftershock state: wait for things to be quiet again.
    default:
      if (piezo[i] > thresholdMin) {
        msec[i] = 0; // keep resetting timer if above threshold
      } else if (msec[i] > aftershockMillis) {
        usbMIDI.sendNoteOff(note[i], 0, channel);
        state[i] = 0; // go back to idle when
      }
  }
}
 
int velocity = map(piezo, thresholdMin, 1023, 80, 127);
You want peak not piezo here.

You're taking the last reading instead of the maximum reading... as it's likely below the threshold min and map doesn't deal with out of range inputs well (I recall but that might not be true for Teensy currently) it's possible that's the source of the velocity problem but I don't think that's likely the only problem or even necessarily involved but it will prevent you from getting a meaningful velocity so you need to change it.

You mention "354 or 738 or 1023" twice as if these are the ONLY values you are seeing... which would be very strange.

Can you post the actual serial output from playing it? (Maybe add a print for the calculated velocity too!)
 
thanks for help!!
with this code I get in serial monitor even if I strike the piezo quite slowly or very hard


begin peak track 1023
peak = 1023
begin peak track 1023
peak = 1023
begin peak track 1023
peak = 1023
begin peak track 1023
peak = 1023
begin peak track 1023
peak = 1023
begin peak track 1023
peak = 1023
begin peak track 1023
peak = 1023
begin peak track 1023
peak = 1023


Code:
/* Use a Piezo sensor (percussion / drum) to send USB MIDI note on
   messages, where the "velocity" represents how hard the Piezo was
   tapped.

   Connect a Pieze sensor to analog pin A0.  This example was tested
   with Murata 7BB-27-4L0.  Almost any piezo sensor (not a buzzer with
   built-in oscillator electronics) may be used.  However, Piezo
   sensors are easily damaged by excessive heat if soldering.  It
   is highly recommended to buy a Piezo with wires already attached!

   Use a 100K resistor between A0 to GND, to give the sensor a "load".
   The value of this resistor determines how "sensitive" the circuit is.

   A pair of 1N4148 diodes are recommended to protect the analog pin.
   The first diode connects to A0 with its stripe (cathode) and the other
   side to GND.  The other diode connects its non-stripe (anode) side to
   A0, and its stripe (cathode) side to 3.3V.

   Sensitivity may also be tuned with the map() function.  Uncomment
   the Serial.print lines to see the actual analog measurements in the
   Arduino Serial Monitor.

   You must select MIDI from the "Tools > USB Type" menu

   This example code is in the public domain.
   *multi-pad extension by oddson (under-tested)*
*/

const int channel = 10;  // General MIDI: channel 10 = percussion sounds
const int PINS = 2;     // number of signals incoming

const int note[PINS] = {36,37};     // array of MIDI note values for read signals

const int analogPin[PINS] = {A0,A1}; //array of analog PINs 
const int thresholdMin = 1;  // minimum reading, avoid noise and false starts
const int peakTrackMillis = 12;
const int aftershockMillis = 17; // 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(115200);
  while (!Serial && millis() < 2500) /* wait for serial monitor */ ;
  Serial.println("Piezo Peak Capture");
}


void loop() {
  for (int i=0;i<PINS;i++){
    delay(5);
    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 (usbMIDI.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) {
        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[i]);
        int velocity = map(peak[i], thresholdMin, 1023, 1, 127);
        usbMIDI.sendNoteOn(note[i], velocity, channel);
        msec[i] = 0;
        state[i] = 2;
      }
      return;

    // Ignore Aftershock state: wait for things to be quiet again.
    default:
      if (piezo[i] > thresholdMin) {
        msec[i] = 0; // keep resetting timer if above threshold
      } else if (msec[i] > aftershockMillis) {
        usbMIDI.sendNoteOff(note[i], 0, channel);
        state[i] = 0; // go back to idle when
      }
  }
}
 
So what are you saying about values other than 1023... it sounds like you have hardware problems.
 
i made change in the code when I was able to get others values but can t remember what did I changed... I m lost with these piezo :)
maybe my piezo are not the good ones?
 
Status
Not open for further replies.
Back
Top