// Various buttons and LEDPin mappings
const int purpleLedPin = 44;
const int whiteLedPin = 45;
const int sequenceButtonPins[] = {A0, A1, A2, A3}; // New sequence selection
const int tempoControlPin = A6;
const int externalClockPin = 42;
const int resetPin = 43;
constexpr int reverseSwitchPin = 13;
constexpr int randomModeSwitchPin = 10;
const int blueLedPin = A4;
const int yellowLedPin = A5;
const int startStopSwitchPin = 11; // Switch pin for start/stop control
const int pageButton = 12;
#define SHIFT595_LATCH_PIN 3 // STCP (Latch)
#define SHIFT595_DATA_PIN 2 // DS (Data)
#define SHIFT595_CLOCK_PIN 4 // SHCP (Clock)
#define SHIFT165_LOAD_PIN 9 // PL (Parallel Load)
#define SHIFT165_CLOCK_PIN 8 // CP (Clock)
#define SHIFT165_DATA_PIN 6 // Q7 (Serial Out)
#define SHIFT165_CE_PIN 7 // CE (Chip Enable)
constexpr uint8_t numSteps = 16; // Number of Steps
constexpr uint8_t numSequences = 4; // Number of Sequences
constexpr unsigned long debounceDelay = 20;
constexpr unsigned long blinkDuration = 50;
constexpr int minBPM = 30;
constexpr int maxBPM = 255;
// Tempo and sequence control
unsigned long stepDurationMs = 300;
unsigned long previousStepTime = 0;
uint8_t currentStep = 0;
bool sequencerRunning = false;
bool randomMode = false; // Track the random mode state
// Sequence storage (4 sequences)
bool storedStates[numSequences][numSteps] = {{false}, {false}, {false}, {false}};
// Button debounce and state tracking
unsigned long lastDebounceTimes[numSteps] = {0};
int buttonStates[numSteps] = {HIGH};
int lastButtonStates[numSteps] = {HIGH};
// Sequence button debounce and state tracking
unsigned long lastSequenceDebounceTimes[numSequences] = {0};
int sequenceButtonStates[numSequences] = {HIGH};
int lastSequenceButtonStates[numSequences] = {HIGH};
// LED blink control
unsigned long blinkStartTime[numSequences] = {0, 0, 0, 0};
bool isBlinking[numSequences] = {false, false, false, false};
// External clock and reset state tracking
int lastExternalClockState = HIGH;
int lastResetState = HIGH;
uint8_t activeSequence = 0; // Track the active sequence
bool currentPage = false; // false = steps 1-8, true = steps 9-16
// Array to map sequence numbers to their corresponding LED pins
const int triggerLedPins[numSequences] = {blueLedPin, yellowLedPin, purpleLedPin, whiteLedPin};
void setup() {
Serial.begin(9600);
// Initialize shift register pins
pinMode(SHIFT595_LATCH_PIN, OUTPUT);
pinMode(SHIFT595_DATA_PIN, OUTPUT);
pinMode(SHIFT595_CLOCK_PIN, OUTPUT);
pinMode(blueLedPin, OUTPUT);
pinMode(yellowLedPin, OUTPUT);
pinMode(SHIFT165_LOAD_PIN, OUTPUT);
pinMode(SHIFT165_CLOCK_PIN, OUTPUT);
pinMode(SHIFT165_DATA_PIN, INPUT);
pinMode(SHIFT165_CE_PIN, OUTPUT);
pinMode(startStopSwitchPin, INPUT_PULLUP);
pinMode(reverseSwitchPin, INPUT_PULLUP);
pinMode(randomModeSwitchPin, INPUT_PULLUP);
// Initialize 74HC165 to load button states
digitalWrite(SHIFT165_LOAD_PIN, LOW);
delayMicroseconds(5);
digitalWrite(SHIFT165_LOAD_PIN, HIGH);
// Initialize button states array to LOW (buttons are pull-up)
for (uint8_t i = 0; i < numSteps; i++) {
buttonStates[i] = LOW;
}
// Initialize page button
pinMode(pageButton, INPUT_PULLUP);
for (uint8_t i = 0; i < numSequences; i++) {
pinMode(sequenceButtonPins[i], INPUT_PULLUP);
}
randomSeed(analogRead(A1)); // Initialize random seed
}
void loop() {
const unsigned long currentTime = millis();
const bool startStopState = digitalRead(startStopSwitchPin);
const int externalClockState = digitalRead(externalClockPin);
const int resetState = digitalRead(resetPin);
const bool reversePlayback = digitalRead(reverseSwitchPin) == LOW;
randomMode = digitalRead(randomModeSwitchPin) == HIGH; // Update random mode state
// Check if the page button is pressed
if (digitalRead(pageButton) == LOW) {
currentPage = !currentPage; // Toggle between steps 1-8 and steps 9-16
}
sequencerRunning = startStopState == HIGH;
updateTempo();
handleSequenceButtons(currentTime);
handleButtons(activeSequence, currentTime);
if (!sequencerRunning) {
handleExternalClock(currentTime, externalClockState, reversePlayback, randomMode);
handleReset(resetState);
}
if (sequencerRunning) {
updateSequencer(currentTime, reversePlayback, randomMode);
}
updateDisplay(activeSequence);
updateTriggerLeds(currentTime);
Serial.println(currentStep);
}
void handleButtons(uint8_t activeSequence, unsigned long currentTime) {
// Load button states from the 74HC165 shift register
digitalWrite(SHIFT165_LOAD_PIN, LOW);
delayMicroseconds(5);
digitalWrite(SHIFT165_LOAD_PIN, HIGH);
for (uint8_t i = 0; i < 8; i++) {
int reading = digitalRead(SHIFT165_DATA_PIN);
// Map the button index to the correct step based on the current page
uint8_t stepIndex = currentPage ? (15 - i) : (7 - i);
if ((currentTime - lastDebounceTimes[stepIndex]) > debounceDelay && reading != buttonStates[stepIndex]) {
buttonStates[stepIndex] = reading;
if (buttonStates[stepIndex] == LOW) {
storedStates[activeSequence][stepIndex] = !storedStates[activeSequence][stepIndex];
}
}
lastButtonStates[stepIndex] = reading;
// Shift in the next button state
digitalWrite(SHIFT165_CLOCK_PIN, HIGH);
digitalWrite(SHIFT165_CLOCK_PIN, LOW);
}
}
void updateDisplay(uint8_t activeSequence) {
const bool* states = storedStates[activeSequence];
uint8_t displayData = 0;
for (uint8_t i = 0; i < 8; i++) {
uint8_t stepIndex = currentPage ? (i + 8) : i; // Calculate once
bool ledState = states[stepIndex];
if (stepIndex == currentStep) {
ledState = !ledState; // Invert for scrolling indicator
}
if (ledState) {
displayData |= (1 << i);
}
}
digitalWrite(SHIFT595_LATCH_PIN, LOW);
shiftOut(SHIFT595_DATA_PIN, SHIFT595_CLOCK_PIN, MSBFIRST, displayData);
digitalWrite(SHIFT595_LATCH_PIN, HIGH);
}
void updateStep(bool reversePlayback, bool randomMode) {
if (randomMode) {
currentStep = random(numSteps);
} else if (reversePlayback) {
currentStep = (currentStep == 0) ? (numSteps - 1) : (currentStep - 1);
} else {
currentStep = (currentStep + 1) % numSteps;
}
}
void triggerStepLEDs(unsigned long currentTime) {
for (uint8_t i = 0; i < numSequences; i++) {
if (storedStates[i][currentStep]) {
blinkStartTime[i] = currentTime;
isBlinking[i] = true;
digitalWrite(triggerLedPins[i], HIGH);
}
}
}
void updateSequencer(unsigned long currentTime, bool reversePlayback, bool randomMode) {
if (currentTime - previousStepTime >= stepDurationMs) {
updateStep(reversePlayback, randomMode);
previousStepTime = currentTime;
triggerStepLEDs(currentTime);
}
}
void handleSequenceButtons(unsigned long currentTime) {
for (uint8_t i = 0; i < numSequences; i++) {
const int reading = digitalRead(sequenceButtonPins[i]);
// Debounce logic
if (reading != lastSequenceButtonStates[i]) {
lastSequenceDebounceTimes[i] = currentTime;
}
if ((currentTime - lastSequenceDebounceTimes[i]) > debounceDelay) {
if (reading != sequenceButtonStates[i]) {
sequenceButtonStates[i] = reading;
// If the button is pressed (LOW), change the active sequence
if (sequenceButtonStates[i] == LOW) {
activeSequence = i;
}
}
}
lastSequenceButtonStates[i] = reading;
}
}
void handleExternalClock(unsigned long currentTime, int externalClockState, bool reversePlayback, bool randomMode) {
if (externalClockState == LOW && lastExternalClockState == HIGH) {
updateStep(reversePlayback, randomMode);
previousStepTime = currentTime;
triggerStepLEDs(currentTime);
}
lastExternalClockState = externalClockState;
}
void handleReset(int resetState) {
if (resetState == LOW && lastResetState == HIGH) {
currentStep = 0;
previousStepTime = millis();
}
lastResetState = resetState;
}
void updateTriggerLeds(unsigned long currentTime) {
for (uint8_t i = 0; i < numSequences; i++) {
if (isBlinking[i] && (currentTime - blinkStartTime[i] >= blinkDuration)) {
digitalWrite(triggerLedPins[i], LOW);
isBlinking[i] = false;
}
}
}
void updateTempo() {
int potValue = analogRead(tempoControlPin);
int bpm = map(potValue, 0, 1023, minBPM, maxBPM);
stepDurationMs = 15000 / bpm; // Equivalent to (60000 / bpm) / 4
}