Please help to get piezo input to drive MIDI note!

I have piezo wired parallel with 1Mohm resistor, 5V zener and small capasitor(47pf) to Arduino abalog pin0.
My goal is to read the piezo input and change MIDI note number according to this input value.
My initial very simple code has good response, but notes are generated very random. Probably because readings are taken in various states of piezo ”ringing”. So the strongest hit does not produce the highest note always.

#include <MIDI.h>
byte note = 0;
byte b = 46; //lowest note
byte c = 1; //MIDI channel
byte d = 3; //MIDI channel offset


const int ledPin = 13;      // led connected to digital pin 13
const int piezo = 0;  // the piezo is connected to analog pin 0



int a=0;


void setup() {
 
  Serial.begin(31250);       // use the midi port
 }

void loop()  {
    a = analogRead(piezo)/8;
   
   if (a > b )
   
 { MIDI.sendNoteOn(a,127,c);
 delay(10);
  MIDI.sendNoteOff(a,0,c);
  delay(100);       


  }
 
}

I think that if the piezo reading would be taken in a period of time and calculated the average it may give me better calculated response. Only I have no idea how to write this.
Can you help!

A few things:

  1. You are not reading the analog pin while the note is playing.
    This is because you read the analog pin, then send the note, delay10 milliseconds, turn off the note, delay 100 milliseconds, THEN read the analog pin. delay() means exactly that: do nothing but wait for the time to expire. Nothing else is performed. Have a look in the IDE example code for the Blink Without Delay sketch. It will show you how to keep running other code while waiting fot the time to expire.

  2. Assuming you get #1 sorted out, you are not reading a note when you do an analog read. If the note you are sending is a sine wave, you will be reading a level SOMEWHERE on the sine wave, than a few microseconds later, another level SOMEWHERE on the sine wave. These levels will vary between the maximum level and the minimum level. If the note is a PWM signal, you will be reading 0 or 5V mostly.

  3. Please show us your exact circuit. A schematic is best, but a clear picture will do if we can easily see all the connections.

Edit: By the way, please modify your post and give it a more descriptive title, perhaps like this one.

Thank you for suggestions!

  1. Yes, I know about the delay(). But this is not that important at the moment for MIDI output. The dancers are not stumping their feet that rapidly, so I hardly miss a hit. And I like to keep the hits at the interval. But it may be important for reading the piezo. I shall look into this.
  2. Yes, that is what I understand as well. Piezo is like a bell. So when I take the reading anywhere during the decay I would not get the accurate one. Thus my thought of collecting the readings for 10ms and calculating the average for final value. And during the this 10ms no note will be sent.
  3. Diagram is attached. But I am looking into different ways of connecting it as well.

I hope this title makes more sense. It is not about MIDI note detection really but more piezo related.

Here is the code with millis() instead of delay(). I think the code reads analogpin0 every 10ms and during the "ring" of the piezo it produses 1-5 notes. The harder the hit the longer is the "ring" and the more decaying note there is. Like if I get hit with the note 127 I get additional 93; 78; 47; 33 in 10ms interval. However it would be great if I get just the first one and ignore the rest. Almost like I had with delay(). Only then, as said I may be in the middle of the "ring" when input is read.

#include <MIDI.h>
byte note = 0;
byte b = 38; //lowest note
byte c = 1; //MIDI channel
byte d = 3; //MIDI channel offset


const int ledPin = 13;      // led connected to digital pin 13
const int piezo = 0;  // the piezo is connected to analog pin 0



int a=0;
int ledState = LOW;             // ledState used to set the LED
long previousMillis = 0;
long interval = 100;  


void setup() {
 
  Serial.begin(31250);       // use the midi port
 
 

 
}

void loop()  {
  unsigned long currentMillis = millis();
 
  if(currentMillis - previousMillis > interval) {

    previousMillis = currentMillis;
    
    a = analogRead(piezo);
    a = map(a, 0, 1023, b, 122);
   
   if (a > b )
   
 { MIDI.sendNoteOn(a,127,c);
 
  MIDI.sendNoteOff(a,0,c);
         


  }
 
}
}

piezo-wiring.jpg

So I have arrived slowly to this.
I needed 2 sensors due technical reasons(the pickup surface is in 2 parts). So I thought of using both piezos to combine the input instead of trying not too successfully amplify the signal. While the setup works kind of, I am not sure if my code does as itended.
I wanted to take 10 readings on analogpin 0 and 1; record these and calculate max1 and max2; combine these two; map the combined value to MIDI note numbers 32(b)-122. I added random number to the lowest note number, so it will not be always the same(it gets played the most and get very annoying). I stayed with delay() - the board doing nothing while the note is sent just seems to be working better.
So could you check the code and tell me if it does what I want and if there is a room for improvement.

#include <MIDI.h>
byte note = 0;
byte c = 1; //MIDI channel
byte f = 3; //MIDI channel offset

const int piezo = 0;  // the piezo is connected to analog pin 0
const int piezo2 = 1;  // the piezo is connected to analog pin 1
long randNumber;

int record[10] = {0,0,0,0,0,0,0,0,0,0}; //record 10 readings in a row
int record_max;  //max of 10 recordings
int record_max2;
int a=0;
int s=0;
int d=0;

int i=0;
int i2=0;
int b=32;        //lowest note allowed to play
int statusLED = 13; // status LED on Arduino

void setup() {
 
  Serial.begin(31250); // use the midi port
 
 

 
}

void loop()  {
  

  
    a = analogRead(piezo);
    s = analogRead(piezo2);
     for(int i=0; i < 10; i++) 
    {
    record[i] = analogRead(piezo);   // read the input pin
    if(record[i] > record_max)
    {
      record_max = record[i];
    }
    }
    a = record_max;                 // record max
    record_max = 0;
    
    for(int i2=0; i2 < 10; i2++) 
    {
    record[i2] = analogRead(piezo2);   // read the input pin
    if(record[i2] > record_max2)
    {
      record_max2 = record[i2];
    }
    }
    s = record_max2;                 // record max2
    record_max2 = 0;
d=a+s;
    d = map(d, 0, 1024, b, 122);    //map recorded and combined max to lowest and highest note
    randNumber = random(1, 8);      //generate random number 1-8
   if (d > 32 + randNumber)         //add random number to the lowest note to make it interesting
   

   
 { MIDI.sendNoteOn(d,127,c); //send note and note off
 
 delay(50);
  MIDI.sendNoteOff(d,0,c);
  delay(50);       


  }
 
}

I did a piezo button circuit years ago. I put the leads through a full wave diode bridge but split the output so I could have the tap and release on different lines. I fed the outputs to NPN transistors (can't remember the resistors if any) that got 5V through 2200R to the collector which seemed to me a good way to limit emitter outputs which went directly to DIGITAL pins. I read the pins in a loop, each read sucked a tiny bit of current and I timed how long the pin was HIGH to get my result. Hard taps took longer. The interesting part is that until I let up on the disc, the flow did not reverse so I could have measured how long I held down. With a little work I could get more detailed strike and release info.

Digital I/O reads in about nothing flat. I can check a pin in a loop about 1 microsecond long.
Analog read takes about 105 micros by itself.

GoForSmoke:
Hmm, interesting approach.
However I need just the hight of a knock and preferably discard everything else. Thus the 10readings average. The difficulty is in need to recognize lower volume knocks as well and use the value of a reading to determine the note number. Also the notes should be played at 100ms rate to avoid miss the beat.
I could think using the time factor in your suggestion and turn this into note number. - the longer the pin is high the higher is the note. But this goes beyond my code writing for seeable future. If you think this would be better solution I may want to hire help to do it.

I don't remember the state of the code last I left it but there's a debug line that says some kind of WIP was going on. I may have to dig up parts and make a new one just to see what I was doing with it last, years ago.

My circuit detected all but the very slightest most careful touch all the way up to screwdriver handle bangs. :grin:

You can look for the largest value in a set of readings with storing them in an array. Doing so just seems to confuse the issue.

Random indenting does
not improve the
readability of your code.

Use the Tools + Auto Format to fix it.

Having global and local variables of the same name is not too bright (i and i2). You don't need two separate local variables for two separate for loops.

You do NOT need so much useless while space in setup() or loop().

    randNumber = random(1, 8);      //generate random number 1-8

When the comment and the code do not agree, the code is right. Always and forever.

d=a+s;
    d = map(d, 0, 1024, b, 122);    //map recorded and combined max to lowest and highest note

What is the range of values that can be in d? Does that range match your from range? (Hint: No).

Another hint: b doesn't mean shit to me. Use names that reflect what the variable is for.

Thanks Paul looking into this.
The random numbers I am creating are like this 1;2;2;6;8;7;7;7..... On what basis the numbers are created I don't know.
If I make this I avoid repeating numbers in a row.

randNumber = random(1, 24);

It seems to be a time frame like one number at given time interval?
"a" and "s" come usually up to 400. Only seldom over 512. So I thought I can discard these with out major concern. Maybe I should not. Tell me.

But the main question for me is: does this code as I have written it read analogpin 0&1; record these; calculate max for both readings; combine these two?

BTW I use single letters or similar to avoid unnecessary typos that are the main causes my code errors and when I need to change the constant value throughout the code. So the letter "b" is for marking the lowest note number and instead of scanning through the whole code(sure it is not too complicated at the moment) to find all the "b"s I just correct the the one at the beginning.

BTW I use single letters or similar to avoid unnecessary typos that are the main causes my code errors

I've found just the opposite. Single letter names are too easy to mis-type, and then the compiler is perfectly happy using the wrong variable's value. YMMV.

"a" and "s" come usually up to 400

Ditch the quotes around variable names. The context of your post should make it clear where you are referring to variable names. If a and s are usually limited to 400, then the code is fine. You need to think about what it is doing. If you have, then it's good.

But the main question for me is: does this code as I have written it read analogpin 0&1; record these; calculate max for both readings; combine these two?

It appears to, in a way more complex than it needs to. The recording part is not necessary.

Thanks to everyone who helped.
We got the first taste on the stage and it worked rather well.

I ended up using this very simple code that worked the best

#include <MIDI.h>
byte note = 0;
byte b = 38; //lowest note ie. threshold
byte c = 1; //MIDI channel
byte d = 3; //MIDI channel offset


const int ledPin = 13;      // led connected to digital pin 13
const int piezo = 0;  // the piezo is connected to analog pin 0
const int piezo2 = 1;  // the piezo is connected to analog pin 1



int a=0; //midi note number from piezo 0
int s=0; //midi note number from piezo 1


void setup() {

  Serial.begin(31250);       // use the midi port
}

void loop()  {
  a = analogRead(piezo);
  delay(50); //delay for keep timing between two piezos
  s = analogRead(piezo2);
  a = map(a, 0, 1023, b, 122); //map analog read to MIDI note range
  s = map(d, 0, 1024, b, 122); 
  if (a > b ) //eliminating notes below threshold

  { 
    MIDI.sendNoteOn(a,127,c);  //send notes according to sensor value
    delay(25);
    MIDI.sendNoteOff(a,0,c);
    delay(25);       


  }
   if (s > b )  //eliminating notes below threshold

  { 
    MIDI.sendNoteOn(s,127,c);  //send notes according to sensor value
    delay(25);
    MIDI.sendNoteOff(s,0,c);
    delay(25);       
 }

}
  s = map(d, 0, 1024, b, 122);

d is a constant. Is there really any reason to map it?

Given that you overwrite the value read from piezo2's pin, is there any reason to read piezo2's pin?

Thanks, Paul.
Your point
I've found just the opposite. Single letter names are too easy to mis-type, and then the compiler is perfectly happy using the wrong variable's value.
is well illustrated. It should be mapping s instead. Strange that it worked at all.
I discarded all recording and smoothing - it seemed to add just erratic behavior. I need to look into this a bit more.
BTW here is a short clip from a performance