Please help a noob figure out his DIY MIDI project

you might benefit from studying state machines. Here is a small introduction to the topic: Yet another Finite State Machine introduction

If I get what you describe correctly, the state machine is pretty simple and could look like this

You start in the IDLE state and listen to the Signal and when the event "Signal > THRESHOLD" becomes true, you record the signal's value that triggered the state change and go to the STRIKE_DETECTED state

In the STRIKE_DETECTED state you keep listening to the Signal and compare it with the previous value (and update it) until the Signal starts to drop in value (assuming it just peaked), at that point you record the time, fire the note and go to a INACTIVE state

in the INACTIVE state you just wait for a calculated timeout and go back to IDLE.

Then you could define an object representing ONE sensor which would encapsulate all the required information.

this could look like this

//Minimum masking time
//TIME MEASURED IN MILLISECONDS
const uint32_t MIN_MASK = 8;

//float that scales max hit value for dynamic mask time between notes
//will be used as follows:
//MASKTIME = MIN_MASK + (analogRead - THRESHOLD) * SIGNAL_SCALER
const double SIGNAL_SCALER = 1.07 ;

//MIDI definitions
const byte NOTE_ON_CMD = 0x90;
const byte NOTE_OFF_CMD = 0x80;
const byte MAX_MIDI_VELOCITY = 127;

class Sensor {
    enum SensorState {IDLE, STRIKE_DETECTED, INACTIVE} ;

  private:
    const char * sensorName;
    byte analogPin;
    byte note;
    uint16_t threshold;
    uint16_t previousSignal;
    SensorState state;
    uint32_t chrono;
    uint32_t duration;

  public:
    // Constructor
    Sensor(const char * name, byte pin, byte n, uint16_t t)
      : sensorName(name), analogPin(pin), note(n), threshold(t), previousSignal(0), state(IDLE)
    {}

    // return true if note was triggered
    bool poll() {
      bool noteSent = false;

      switch (state) {
        case IDLE: {
            analogRead(analogPin);                    // trow away one value for analog stability
            uint16_t signal = analogRead(analogPin);
            if (signal >= threshold) {
              previousSignal = signal;
              state = STRIKE_DETECTED;
            }
          }
          break;

        case STRIKE_DETECTED:
          {
            analogRead(analogPin);                    // trow away one value for analog stability
            uint16_t signal = analogRead(analogPin);
            if (signal < previousSignal) { // Signal starts decreasing
              noteFire(previousSignal); // is it OK to use the signal level as a velocity ?
              noteSent = true;
              chrono = millis();
              duration = MIN_MASK + (signal - threshold) * SIGNAL_SCALER;
              state = INACTIVE;
            } else {
              previousSignal = signal;
            }
          }
          break;

        case INACTIVE:
          if (millis() - chrono >= duration) state = IDLE;
          break;
      }

      return noteSent;
    }

    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);
    }

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

// ---------------- THE MAIN CODE ---------------

//MIDI baud rate
#define BAUD_RATE 115200

//This should work for all triggers, and if not, change resistor used for that trigger
#define THRESHOLD 10

//MIDI note definitions for each trigger
const byte SNARE_NOTE = 38;
const byte BASS_NOTE = 36;
const byte ACOUSTIC_SNARE_NOTE = 38;
const byte CLOSED_HI_HAT_NOTE = 42;
const byte OPEN_HI_HAT_NOTE = 46;
const byte CRASH_CYMBAL_NOTE = 49; // Corrected to 49 for Crash Cymbal 1

Sensor sensors[] = {
  {"Snare",           A0, SNARE_NOTE, THRESHOLD},
  {"Bass",            A1, BASS_NOTE, THRESHOLD},
  {"Acoustic",        A2, ACOUSTIC_SNARE_NOTE, THRESHOLD},
  {"Closed Hi-Hat",   A3, CLOSED_HI_HAT_NOTE, THRESHOLD},
  {"Open Hi-Hat",     A4, OPEN_HI_HAT_NOTE, THRESHOLD},
  {"Crash Cymbal",    A5, CRASH_CYMBAL_NOTE, THRESHOLD}
};

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

void loop() {
  for (auto & s : sensors) s.poll();
}

or something like this (fully untested, typed here)

the advantage with the class is you do the work once and the main code becomes super simple:

you define the sensors

Sensor sensors[] = {
  {"Snare",           A0, SNARE_NOTE, THRESHOLD},
  {"Bass",            A1, BASS_NOTE, THRESHOLD},
  {"Acoustic",        A2, ACOUSTIC_SNARE_NOTE, THRESHOLD},
  {"Closed Hi-Hat",   A3, CLOSED_HI_HAT_NOTE, THRESHOLD},
  {"Open Hi-Hat",     A4, OPEN_HI_HAT_NOTE, THRESHOLD},
  {"Crash Cymbal",    A5, CRASH_CYMBAL_NOTE, THRESHOLD}
};

and the code just polls them in turn. that's it :slight_smile:

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

void loop() {
  for (auto & s : sensors) s.poll();
}

(the name of the sensors is not really needed, it might be useful for debugging or whatever if you want to print which sensor got triggered. Not needed in the final version)