Help Making Drum Controller With Arduino Nano & 16 Channel Multiplexer

Hello everyone!

I'm currently working on a personal project to build a drum controller using an Arduino Nano and a 16-channel multiplexer. I've designed the schematic and circuit myself, which you can see in the attached image.

Below is the current code running on my Arduino:

#include <light_CD74HC4067.h>

#include <MIDI.h>

MIDI_CREATE_DEFAULT_INSTANCE();

// Direct analog and digital pins
const int SNARE_PIN = A1;
const int TCRT5000_PEDAL_PIN = A4; // TCRT5000 for hi-hat pedal

const int KICK_PIN = 10;
const int BELL_RIDE_PIN = 9;
const int CHOKE_CRASH1_PIN = 6;
const int CHOKE_CRASH2_PIN = 7;
const int CHOKE_RIDE_EDGE_PIN = 8; // This pin can be for ride choke or ride edge

// Selector pins for Multiplexer
const int S0_MUX_PIN = 2;
const int S1_MUX_PIN = 3;
const int S2_MUX_PIN = 4;
const int S3_MUX_PIN = 5;

// Analog input pin for Multiplexer
const int MUX_INPUT_PIN = A0;

CD74HC4067 mux(4, 5, 6, 7);  // create a new CD74HC4067 object with its four select lines

const int signal_pin = A0;

// MIDI Channel
const int MIDICH = 2;

// Drum Notes (these remain the same from previous code)
const int NOTE_SNARE_DRUM = 38;
const int NOTE_CLOSED_HIHAT = 42;
const int NOTE_PEDAL_HIHAT = 44; // This might not be used if full CC is implemented
const int NOTE_OPEN_HIHAT = 46;
const int NOTE_CRASH1 = 49;
const int NOTE_CRASH2 = 57;
const int NOTE_TOM1 = 50;
const int NOTE_TOM2 = 47;
const int NOTE_FLOOR = 43;
const int NOTE_KICK = 36;
const int NOTE_RIDE = 51;

// Additional notes for Addictive Drums (example)
const int NOTE_CRASH1_CHOKE = 112; // Example: Note for crash1 choke (adjust according to AD)
const int NOTE_CRASH2_CHOKE = 111; // Example: Note for crash2 choke (adjust according to AD)
const int NOTE_RIDE_CHOKE = 115; // Example: Note for ride choke (adjust according to AD)
const int NOTE_RIDE_EDGE = 53; // Example: Note for ride edge (adjust according to AD)
const int NOTE_BELL_RIDE = 54; // Example: Note for bell ride (adjust according to AD)

// For Hi-Hat MIDI CC
const int HIHAT_CC_NUMBER = 4; // CC number for hi-hat pedal (standard usually CC 4 or 64)
const int HIHAT_OPEN_THRESHOLD = 900; // Analog TCRT5000 value above this = open hi-hat
const int HIHAT_CLOSED_THRESHOLD = 200; // Analog TCRT5000 value below this = closed hi-hat

// Sensitivity multiplication
const int Mulvel = 10; // For regular analog pads
const int hhMul = 80; // For hi-hat pedal (can be adjusted)

// Individual pad sensitivities
const int snrMinVel = 5;
const int hhtMinVel = 7; // Minimum analog value for hi-hat (from multiplexer)
const int csh1MinVel = 7;
const int csh2MinVel = 7;
const int rdeMinVel = 7;
const int tom1MinVel = 7;
const int tom2MinVel = 7;
const int flrMinVel = 7;
const int rimMinVel = 7; // Minimum analog value for rim

// Variables to store last trigger time
static unsigned long lastKickTriggerTime = 0;
static unsigned long lastBellRideTriggerTime = 0; // FIX: Added this declaration
const unsigned long RETRIGGER_DELAY_MS = 100; // Retrigger delay for digital input

// Variables to store hi-hat pedal status
int lastPedalAnalogValue = 0; // For TCRT5000, analog value
int lastSentCCValue = -1; // To store the last sent CC value
const int PEDAL_ANALOG_RANGE = 1023; // Analog value range from 0-1023

// Variables to store choke status
static unsigned long lastChokeCrash1TriggerTime = 0;
static unsigned long lastChokeCrash2TriggerTime = 0;
static unsigned long lastChokeRideTriggerTime = 0;


// Function to select multiplexer channel
void setMuxChannel(int channel) {
  digitalWrite(S0_MUX_PIN, bitRead(channel, 0));
  digitalWrite(S1_MUX_PIN, bitRead(channel, 1));
  digitalWrite(S2_MUX_PIN, bitRead(channel, 2));
  digitalWrite(S3_MUX_PIN, bitRead(channel, 3));
}

void setup() {
  // Setup analog pins
  pinMode(SNARE_PIN, INPUT);
  pinMode(TCRT5000_PEDAL_PIN, INPUT);
  pinMode(MUX_INPUT_PIN, INPUT);

  // Setup digital pins
  pinMode(KICK_PIN, INPUT_PULLUP);
  pinMode(BELL_RIDE_PIN, INPUT_PULLUP);
  pinMode(CHOKE_CRASH1_PIN, INPUT_PULLUP);
  pinMode(CHOKE_CRASH2_PIN, INPUT_PULLUP);
  pinMode(CHOKE_RIDE_EDGE_PIN, INPUT_PULLUP);

  // Setup multiplexer selector pins
  pinMode(S0_MUX_PIN, OUTPUT);
  pinMode(S1_MUX_PIN, OUTPUT);
  pinMode(S2_MUX_PIN, OUTPUT);
  pinMode(S3_MUX_PIN, OUTPUT);

  pinMode(signal_pin, INPUT);

  // Start MIDI and Serial communication
  MIDI.begin(MIDICH);
  Serial.begin(115200);
}

// void test() {
//     for (byte i = 0; i < 16; i++) {
//         mux.channel(i);
//         int val = analogRead(signal_pin);           // Read analog value
//         Serial.println("Channel "+String(i)+": "+String(val));  // Print value
//         delay(500);
//     }
//   delay(2000);
// }


void loop() {
  unsigned long currentMillis = millis(); // Get current time

  // --- Analog Pad Reading (non-multiplexed) ---
  // Snare
  int snareVal = analogRead(SNARE_PIN);
  if (snareVal > snrMinVel) {
    int snrVelocity = snareVal * Mulvel;
    if (snrVelocity > 127) snrVelocity = 127;

    MIDI.sendNoteOn(NOTE_SNARE_DRUM, snrVelocity, MIDICH);
    delay(50);
    MIDI.sendNoteOff(NOTE_SNARE_DRUM, 0, MIDICH);
  }

  // --- Multiplexed Pads Reading ---
  // Multiplexer channel order: rim, crash1, crash2, ride, tom1, tom2, floor, hihat
  // Channel 0: Rim (assumed)
  setMuxChannel(0);
  delayMicroseconds(10); // Wait a bit after changing channel
  int rimVal = analogRead(MUX_INPUT_PIN);
  if (rimVal > rimMinVel) {
    int rimVelocity = rimVal * Mulvel;
    if (rimVelocity > 127) rimVelocity = 127;
    // MIDI.sendNoteOn(NOTE_RIM, rimVelocity, MIDICH); // You need to define NOTE_RIM
    // delay(50);
    // MIDI.sendNoteOff(NOTE_RIM, 0, MIDICH);
  }

  // Channel 1: Crash 1
  setMuxChannel(1);
  delayMicroseconds(10);
  int crash1Val = analogRead(MUX_INPUT_PIN);
  if (crash1Val > csh1MinVel) {
    int crash1Velocity = crash1Val * Mulvel;
    if (crash1Velocity > 127) crash1Velocity = 127;
    MIDI.sendNoteOn(NOTE_CRASH1, crash1Velocity, MIDICH);
    delay(50);
    MIDI.sendNoteOff(NOTE_CRASH1, 0, MIDICH);
  }

  // Channel 2: Crash 2
  setMuxChannel(2);
  delayMicroseconds(10);
  int crash2Val = analogRead(MUX_INPUT_PIN);
  if (crash2Val > csh2MinVel) {
    int crash2Velocity = crash2Val * Mulvel;
    if (crash2Velocity > 127) crash2Velocity = 127;
    MIDI.sendNoteOn(NOTE_CRASH2, crash2Velocity, MIDICH);
    delay(50);
    MIDI.sendNoteOff(NOTE_CRASH2, 0, MIDICH);
  }

  // Channel 3: Ride
  setMuxChannel(3);
  delayMicroseconds(10);
  int rideVal = analogRead(MUX_INPUT_PIN);
  if (rideVal > rdeMinVel) {
    int rideVelocity = rideVal * Mulvel;
    if (rideVelocity > 127) rideVelocity = 127;
    MIDI.sendNoteOn(NOTE_RIDE, rideVelocity, MIDICH);
    delay(50);
    MIDI.sendNoteOff(NOTE_RIDE, 0, MIDICH);
  }

  // Channel 4: Tom 1
  setMuxChannel(4);
  delayMicroseconds(10);
  int tom1Val = analogRead(MUX_INPUT_PIN);
  if (tom1Val > tom1MinVel) {
    int tom1Velocity = tom1Val * Mulvel;
    if (tom1Velocity > 127) tom1Velocity = 127;
    MIDI.sendNoteOn(NOTE_TOM1, tom1Velocity, MIDICH);
    delay(50);
    MIDI.sendNoteOff(NOTE_TOM1, 0, MIDICH);
  }

  // Channel 5: Tom 2
  setMuxChannel(5);
  delayMicroseconds(10);
  int tom2Val = analogRead(MUX_INPUT_PIN);
  if (tom2Val > tom2MinVel) {
    int tom2Velocity = tom2Val * Mulvel;
    if (tom2Velocity > 127) tom2Velocity = 127;
    MIDI.sendNoteOn(NOTE_TOM2, tom2Velocity, MIDICH);
    delay(50);
    MIDI.sendNoteOff(NOTE_TOM2, 0, MIDICH);
  }

  // Channel 6: Floor Drum
  setMuxChannel(6);
  delayMicroseconds(10);
  int floordrmVal = analogRead(MUX_INPUT_PIN);
  if (floordrmVal > flrMinVel) {
    int floordrmVelocity = floordrmVal * Mulvel;
    if (floordrmVelocity > 127) floordrmVelocity = 127;
    MIDI.sendNoteOn(NOTE_FLOOR, floordrmVelocity, MIDICH);
    delay(50);
    MIDI.sendNoteOff(NOTE_FLOOR, 0, MIDICH);
  }

  // Channel 7: Hi-hat (Piezo sensor on hi-hat pad)
  setMuxChannel(7);
  delayMicroseconds(10);
  int hihatVal = analogRead(MUX_INPUT_PIN);

  // --- Hi-Hat Pedal Reading (TCRT5000) and MIDI CC ---
  int pedal_analog_value = analogRead(TCRT5000_PEDAL_PIN);

  // Convert analog value (0-1023) to MIDI CC value (0-127)
  // Inverse mapping: high analog value (closed) becomes low CC value (0-63), and vice versa
  // Or direct mapping: high analog value (closed) becomes high CC value (64-127)
  // Depends on how Addictive Drums interprets CC.
  // For example, if high analog value = closed, we map to CC 0 (closed)
  // and low analog value = open, we map to CC 127 (open).
  int cc_value = map(pedal_analog_value, 0, PEDAL_ANALOG_RANGE, 0, 127); // Invert CC value
  if (cc_value < 0) cc_value = 0;
  if (cc_value > 127) cc_value = 127;

  // Send MIDI CC only if there's a significant change in CC value
  // This avoids sending excessive CC messages
  if (abs(cc_value - lastSentCCValue) > 1) { // Change more than 1 CC unit
    MIDI.sendControlChange(HIHAT_CC_NUMBER, cc_value, MIDICH);
    lastSentCCValue = cc_value;
    // Serial.print("Hi-Hat CC: ");
    // Serial.println(cc_value);
  }

  // Hi-hat pad reading (from piezo pad)
  if (hihatVal > hhtMinVel) {
    int hihatVelocity = hihatVal * Mulvel;
    if (hihatVelocity > 127) hihatVelocity = 127;

    // Determine if the pedal is more closed or open from the analog value
    // This is to select the appropriate Note On (closed/open hihat)
    if (pedal_analog_value > HIHAT_OPEN_THRESHOLD) { // Pedal is more open
        MIDI.sendNoteOn(NOTE_OPEN_HIHAT, hihatVelocity, MIDICH);
        delay(50);
        MIDI.sendNoteOff(NOTE_OPEN_HIHAT, 0, MIDICH);
    } else if (pedal_analog_value < HIHAT_CLOSED_THRESHOLD) { // Pedal is more closed
        MIDI.sendNoteOn(NOTE_CLOSED_HIHAT, hihatVelocity, MIDICH);
        delay(50);
        MIDI.sendNoteOff(NOTE_CLOSED_HIHAT, 0, MIDICH);
    } else { // Between open and closed (e.g., semi-open)
        // Can send Note On for semi-open hihat if available, or default to closed
        MIDI.sendNoteOn(NOTE_CLOSED_HIHAT, hihatVelocity, MIDICH);
        delay(50);
        MIDI.sendNoteOff(NOTE_CLOSED_HIHAT, 0, MIDICH);
    }
  }


  // --- Digital Input Reading ---

  // Kick
  static int lastKickState = LOW;
  int kickState = digitalRead(KICK_PIN);
  if (kickState == HIGH && lastKickState == LOW) {
    if (currentMillis - lastKickTriggerTime > RETRIGGER_DELAY_MS) {
      MIDI.sendNoteOn(NOTE_KICK, 100, MIDICH);
      delay(50);
      MIDI.sendNoteOff(NOTE_KICK, 0, MIDICH);
      lastKickTriggerTime = currentMillis;
    }
  }
  lastKickState = kickState;

  // Bell Ride
  static int lastBellRideState = LOW;
  int bellRideState = digitalRead(BELL_RIDE_PIN);
  if (bellRideState == HIGH && lastBellRideState == LOW) {
    if (currentMillis - lastBellRideTriggerTime > RETRIGGER_DELAY_MS) {
      MIDI.sendNoteOn(NOTE_BELL_RIDE, 100, MIDICH);
      delay(50);
      MIDI.sendNoteOff(NOTE_BELL_RIDE, 0, MIDICH);
      lastBellRideTriggerTime = currentMillis;
    }
  }
  lastBellRideState = bellRideState;

  // Choke Crash 1
  static int lastChokeCrash1State = LOW;
  int chokeCrash1State = digitalRead(CHOKE_CRASH1_PIN);
  if (chokeCrash1State == HIGH && lastChokeCrash1State == LOW) {
    if (currentMillis - lastChokeCrash1TriggerTime > RETRIGGER_DELAY_MS) {
      MIDI.sendNoteOn(NOTE_CRASH1_CHOKE, 100, MIDICH); // Send choke note for crash 1
      delay(50);
      MIDI.sendNoteOff(NOTE_CRASH1_CHOKE, 0, MIDICH); // Note off (if necessary)
      lastChokeCrash1TriggerTime = currentMillis;
    }
  }
  lastChokeCrash1State = chokeCrash1State;

  // Choke Crash 2
  static int lastChokeCrash2State = LOW;
  int chokeCrash2State = digitalRead(CHOKE_CRASH2_PIN);
  if (chokeCrash2State == HIGH && lastChokeCrash2State == LOW) {
    if (currentMillis - lastChokeCrash2TriggerTime > RETRIGGER_DELAY_MS) {
      MIDI.sendNoteOn(NOTE_CRASH2_CHOKE, 100, MIDICH); // Send choke note for crash 2
      delay(50);
      MIDI.sendNoteOff(NOTE_CRASH2_CHOKE, 0, MIDICH);
      lastChokeCrash2TriggerTime = currentMillis;
    }
  }
  lastChokeCrash2State = chokeCrash2State;

  // Choke Ride / Edge Ride Logic
  static int lastChokeRideEdgeState = LOW;
  int chokeRideEdgeState = digitalRead(CHOKE_RIDE_EDGE_PIN);
  bool isRidePadHit = (rideVal > rdeMinVel); // Check if ride pad is also hit
  // Note: isBellRideHit here will read BELL_RIDE_PIN every loop, not just when pressed
  // For more accuracy, you can store bell ride status like kick.
  bool isBellRideCurrentlyPressed = (digitalRead(BELL_RIDE_PIN) == HIGH);

  if (chokeRideEdgeState == HIGH && lastChokeRideEdgeState == LOW) {
    if (currentMillis - lastChokeRideTriggerTime > RETRIGGER_DELAY_MS) {
      if (isRidePadHit || isBellRideCurrentlyPressed) { // If choke is pressed SIMULTANEOUSLY with pad or bell
        MIDI.sendNoteOn(NOTE_RIDE_EDGE, 100, MIDICH); // Send note for Edge Ride
        //Serial.println("Edge Ride!");
      } else { // If only choke is pressed
        MIDI.sendNoteOn(NOTE_RIDE_CHOKE, 100, MIDICH); // Send note for Choke Ride
        //Serial.println("Choke Ride!");
      }
      delay(50);
      MIDI.sendNoteOff(NOTE_RIDE_EDGE, 0, MIDICH);
      MIDI.sendNoteOff(NOTE_RIDE_CHOKE, 0, MIDICH);
      lastChokeRideTriggerTime = currentMillis;
    }
  }
  lastChokeRideEdgeState = chokeRideEdgeState;

}

However, I'm encountering a significant issue with the multiplexer. Some channels are showing unusually high and random noise levels. I tested all multiplexer channels yesterday, and here are the results I got (channels 8-15 were also connected at that time, using the same circuit):

CH0: 0
CH1: 0
CH2: 0
CH3: 0
CH4: 0
CH5: 0
CH6: 0
CH7: 0
CH8: 568
CH9: 554
CH10: 559
CH11: 554
CH12: 553
CH13: 559
CH14: 557
CH15: 560

But then, today, when I retested, the results were quite surprising and different:

Channel 0: 0
Channel 1: 0
Channel 2: 889
Channel 3: 909
Channel 4: 0
Channel 5: 0
Channel 6: 893
Channel 7: 913
Channel 8: 0
Channel 9: 0
Channel 10: 892
Channel 11: 910
Channel 12: 0
Channel 13: 0
Channel 14: 894
Channel 15: 912

I'm completely stumped as to what might be causing this random noise on various multiplexer channels. The circuits are exactly the same, with the only difference being that channels 8-15 were not actively used today (though they were yesterday).
Any insights or suggestions on how to troubleshoot this issue would be greatly appreciated!

That is not the way to wire up the sensors they should be wired up like this:-

Note the 1M resistor should be across the input not in series with it.

Where did you get the configuration that you used?

1 Like

Sorry I cannot follow your pictures and I have nothing that looks like them. Post an annotated schematic with part numbers. Show all connections and power sources.

1 Like

Does that effecting the random value from the multiplexer?

Channel 0: 0
Channel 1: 0
Channel 2: 889
Channel 3: 909
Channel 4: 0
Channel 5: 0
Channel 6: 893
Channel 7: 913
Channel 8: 0
Channel 9: 0
Channel 10: 892
Channel 11: 910
Channel 12: 0
Channel 13: 0
Channel 14: 894
Channel 15: 912

for the circuits i took the worst possible answer, i used chat gpt :sweat_smile:

sorry took too long to reply :slight_smile:

Okay i will break down the all the connections i used

For Piezos / Piezi? :

  • The (+) wire from the piezo goes to 1st pin of 1M resistor
  • 2nd pin from 1M resistor goes to (-) pin zener diode 5v1 and first pin 10K resistor and also goes to Arduino Analog pin or one of input in 16CH Multiplexer (CD74HC4067)
  • The (+) pin from zener diode 5v1 is going to GND and the 2nd pin from the 10K resistor is also going into GND

For The 16CH Multiplexer (CD74HC4067) :

  • SIG pin -> A0 Arduino nano
  • S0 - S3 -> Digital Pin 2,3,4,5 Arduino nano
  • EN -> GND
  • VCC -> 5v Arduino
  • GND -> GND

Hmm i see that circuits is using 3v3 zener diode, is it okay if im using 5v1 instead??

i have searched 1N4868, but i only find 1N4148 not 1N4868

Wait, what is going on here?
One day it prints CH0: the next day it prints Channel 0: and the code you posted does neither!

2 Likes

Oh yeah i changed the code for the testing but only the CH & Channel text but the rest is the same

there is this part where i commented out the code

So for testing you used max.channel() to select the channel but in the code you set the pins individually. Why?

2 Likes

Is there going to have any Difference between that? i used that

library for the testing and

this one to select the channel for the individual channel

Not really but in your code you wait 10us before you do the analogRead.

1 Like

well yeah, but i think its not going to make a big difference when scaning each channel on the multiplexer. the big problem is i have messed up circuits, because i buy a brand new multiplexer to test if the multiplexer i already own is faulty, but the result is still same, i just tested it today because the new one just arrived :slight_smile:

Make your circuits like this:

What diodes do you now have?

1 Like

currently i have zener diodes 5v1, resistor 10K, and resistor 1M just like on this picture

anyways i also confused because there is 2 different circuit designs one from

and one from you, what is the difference between that two circuits?

which one should i use?? i also have 1K resistor in stock

Hi, @morishima_kureo
Welcome to the forum.

Can you please post a copy of your circuit, a picture of a hand drawn circuit in jpg, png?
Hand drawn and photographed is perfectly acceptable.
Please include ALL hardware, power supplies, component names and pin labels.

The cut and paste image version is not suitable for trouble shooting purposes.

Thanks.. Tom.... :smiley: :+1: :coffee: :australia:

1 Like

Up to you but you do need the 1N4148

2 Likes

Hello, Nice to Meet you :grin:

I will try to draw that, but maybe if you want you can take a look at my design at the cirkit. this is the link to the project https://app.cirkitdesigner.com/project/e65482c7-3639-4e1f-bc27-885b8dff33a2 (im not good at drawing electronic circuits still newbie to this thing and this is my first project using arduino)