#include <MIDI.h>
#include <TM1637Display.h>
#include <Encoder.h>
// === MIDI Channel Definitions ===
#define MIDI_CHANNEL_DRUMS 10 // Channel for drum triggers (Kick, Snare, etc.)
// === Digital Display Pins ===
#define TM1637_CLK 6
#define TM1637_DIO 7
// === Rotary Encoder Pins ===
#define ENCODER_CLK_PIN 4
#define ENCODER_DT_PIN 3
#define ENCODER_SW_PIN 2
// === Shift Register Pins ===
#define LATCH_PIN 12
#define CLOCK_PIN 11
#define DATA_PIN 10 // Q7 output from last shift register
// === LED Pin ===
#define LED_PIN 13
// === Shift Register Bit Assignments ===
#define START_STOP_BIT 8 // D0 of SR1
#define TAP_TEMPO_BIT 9 // D1 of SR1
#define KICK_DRUM_BIT 10 // D2 of SR1
#define SNARE_DRUM_BIT 11 // D3 of SR1
#define TEST_BUTTON_BIT 0 // D0 of SR2
// === MIDI Instance ===
MIDI_CREATE_DEFAULT_INSTANCE();
// === Display and Encoder Instances ===
TM1637Display display(TM1637_CLK, TM1637_DIO);
Encoder myEncoder(ENCODER_DT_PIN, ENCODER_CLK_PIN);
// === Global Variables ===
int tempo = 120;
bool isRunning = false;
unsigned long lastTickTime = 0;
unsigned long interval;
bool lastStartStopState = HIGH;
bool lastTapTempoState = HIGH;
int clockTickCount = 0;
// === Toggle States ===
bool kickDrumState = false;
bool lastKickDrumButtonState = HIGH;
bool snareDrumState = false;
bool lastSnareDrumButtonState = HIGH;
void setup() {
Serial.begin(31250); // MIDI baud rate
pinMode(LATCH_PIN, OUTPUT);
pinMode(CLOCK_PIN, OUTPUT);
pinMode(DATA_PIN, INPUT);
pinMode(ENCODER_CLK_PIN, INPUT);
pinMode(ENCODER_DT_PIN, INPUT);
pinMode(ENCODER_SW_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
calculateInterval();
display.setBrightness(0x0f);
myEncoder.write(tempo * 4);
updateDisplay();
}
void loop() {
unsigned int shiftRegisterState = readShiftRegisters();
handleStartStopButton(shiftRegisterState);
handleTapTempoButton(shiftRegisterState);
handleKickDrumTrigger(shiftRegisterState);
handleSnareDrumTrigger(shiftRegisterState);
handleTestButton(shiftRegisterState); // 🆕 Test button on SR2
handleRotaryEncoder();
if (digitalRead(ENCODER_SW_PIN) == LOW) {
tapTempo();
delay(250);
}
if (millis() - lastTickTime >= interval) {
MIDI.sendClock();
lastTickTime = millis();
clockTickCount++;
if (clockTickCount >= 24) {
clockTickCount = 0;
digitalWrite(LED_PIN, HIGH);
delay(30);
digitalWrite(LED_PIN, LOW);
updateDisplay();
}
}
}
unsigned int readShiftRegisters() {
digitalWrite(LATCH_PIN, LOW);
delayMicroseconds(5);
digitalWrite(LATCH_PIN, HIGH);
unsigned int data = 0;
for (int i = 0; i < 16; i++) {
digitalWrite(CLOCK_PIN, LOW);
delayMicroseconds(1);
data |= (digitalRead(DATA_PIN) << (15 - i));
digitalWrite(CLOCK_PIN, HIGH);
delayMicroseconds(1);
}
return data;
}
void handleStartStopButton(unsigned int shiftRegisterState) {
static unsigned long lastDebounceTime = 0;
bool currentStartStopState = bitRead(shiftRegisterState, START_STOP_BIT);
if (currentStartStopState == HIGH && lastStartStopState == LOW) {
unsigned long currentTime = millis();
if (currentTime - lastDebounceTime >= 500) {
isRunning = !isRunning;
if (isRunning) {
MIDI.sendStart();
Serial.write("MIDI Clock STARTED\n");
} else {
MIDI.sendStop();
Serial.write("MIDI Clock STOPPED\n");
}
lastDebounceTime = currentTime;
}
}
lastStartStopState = currentStartStopState;
}
void handleTapTempoButton(unsigned int shiftRegisterState) {
static unsigned long lastDebounceTime = 0;
bool currentTapTempoState = bitRead(shiftRegisterState, TAP_TEMPO_BIT);
if (currentTapTempoState == HIGH && lastTapTempoState == LOW) {
unsigned long currentTime = millis();
if (currentTime - lastDebounceTime >= 250) {
tapTempo();
lastDebounceTime = currentTime;
}
}
lastTapTempoState = currentTapTempoState;
}
void handleKickDrumTrigger(unsigned int shiftRegisterState) {
static bool buttonArmed = true;
static unsigned long lastDebounceTime = 0;
static bool lastKickState = HIGH;
bool currentKickButtonState = bitRead(shiftRegisterState, KICK_DRUM_BIT);
unsigned long currentTime = millis();
if (currentKickButtonState != lastKickState) {
lastDebounceTime = currentTime;
}
if ((currentTime - lastDebounceTime) > 50) {
if (currentKickButtonState == HIGH && buttonArmed) {
kickDrumState = !kickDrumState;
int value = kickDrumState ? 127 : 0;
MIDI.sendControlChange(24, value, MIDI_CHANNEL_DRUMS);
Serial.print("Kick Drum CC24 Sent: ");
Serial.println(value);
buttonArmed = false;
}
if (currentKickButtonState == LOW) {
buttonArmed = true;
}
}
lastKickState = currentKickButtonState;
}
void handleSnareDrumTrigger(unsigned int shiftRegisterState) {
static bool buttonArmed = true;
static unsigned long lastDebounceTime = 0;
static bool lastSnareState = HIGH;
bool currentSnareButtonState = bitRead(shiftRegisterState, SNARE_DRUM_BIT);
unsigned long currentTime = millis();
if (currentSnareButtonState != lastSnareState) {
lastDebounceTime = currentTime;
}
if ((currentTime - lastDebounceTime) > 50) {
if (currentSnareButtonState == HIGH && buttonArmed) {
snareDrumState = !snareDrumState;
int value = snareDrumState ? 127 : 0;
MIDI.sendControlChange(29, value, MIDI_CHANNEL_DRUMS);
Serial.print("Snare Drum CC29 Sent: ");
Serial.println(value);
buttonArmed = false;
}
if (currentSnareButtonState == LOW) {
buttonArmed = true;
}
}
lastSnareState = currentSnareButtonState;
}
// 🆕 TEST BUTTON HANDLER
void handleTestButton(unsigned int shiftRegisterState) {
static bool lastTestButtonState = HIGH;
bool currentTestButtonState = bitRead(shiftRegisterState, TEST_BUTTON_BIT);
if (currentTestButtonState == LOW && lastTestButtonState == HIGH) {
Serial.println("Test");
}
lastTestButtonState = currentTestButtonState;
}
void handleRotaryEncoder() {
static long lastRawPosition = myEncoder.read();
static int stepRemainder = 0;
long currentRawPosition = myEncoder.read();
int stepChange = currentRawPosition - lastRawPosition;
if (stepChange != 0) {
stepRemainder += stepChange;
int fullSteps = stepRemainder / 4;
stepRemainder %= 4;
if (fullSteps != 0) {
tempo += fullSteps;
if (tempo < 30) tempo = 30;
if (tempo > 300) tempo = 300;
calculateInterval();
updateDisplay();
myEncoder.write(tempo * 4);
lastRawPosition = myEncoder.read();
stepRemainder = 0;
} else {
lastRawPosition = currentRawPosition;
}
}
}
void calculateInterval() {
interval = 60000 / (tempo * 24);
}
void tapTempo() {
static unsigned long lastTapTime = 0;
unsigned long currentTime = millis();
if (lastTapTime != 0) {
unsigned long tapInterval = currentTime - lastTapTime;
tempo = 60000 / tapInterval;
if (tempo < 30) tempo = 30;
if (tempo > 300) tempo = 300;
calculateInterval();
updateDisplay();
}
lastTapTime = currentTime;
}
void updateDisplay() {
display.showNumberDec(tempo, true);
char buffer[20];
sprintf(buffer, "Tempo: %d BPM", tempo);
Serial.println(buffer);
}