Making funky little things ❤️

The last two weeks I've been putting a lot of time into one of my music-related side projects. For a few years on/off I've been building tools and devices that change the way an analog synth presents its sound and its modulation outwardly.
This week I made an Arduino program that operates a row of four LEDs lights that respond to CV and gate signals output from my synthesizer.
I'll share my code and wiring below in case anyone is looking for help with a similar project. (Goodness knows I've found much help from random websites and blogs about other people's programming and hardware projects.)
AI was a valuable resource in writing the code, but it's taken a bit of tinkering to get it working exactly the way I hoped. It works by recording a value from the CV signal if/when it sees the Gate signal cross a certain threshold. It then stores the 4 most recent values in an array, and copies that array into another array sorted by magnitude. It uses that sorted array to determine which LED(s) should receive an output signal, prefering to keep a given signal mapped to whatever LED most recently had a signal closest to it. This is great for arpeggios and patterns. It maps up and downward motion well, too. after playing a sequence of four notes a few times, it will "learn" that pattern and sort it to a sequence of LEDs that track the up/ down movement of those pitches.
The code works well- and it's proven to be surprisingly flexible! I've experimented with changing the input sources from Gate/CV to other patch points on my Moog Grandmother, and each seems to respond in a unique and responsive way- I'm excited to try preforming with this!
Operating LED lights is not my end goal for this project, but it's a good proof of concept, and it's pretty cool in it's own right!
#include "SoftwareSerial.h"
#include "AccelStepper.h"
const int numLights = 4;
const int ledPins[numLights] = {2, 3, 4, 5}; // Digital pins for LEDs
const int analogPin1 = A0; // Analog pin for signal input
const int analogPin2 = A1; // Analog pin for blink control
int signalValuesByAge[numLights] = {0, 0, 0, 0}; // Array to store the signal values by age
int signalValuesByMagnitude[numLights] = {0, 0, 0, 0}; // Array to store the signal values by magnitude
int currentIndex = 0; // Index to keep track of the oldest value
int currentActiveLED = -1; // Variable to store the index of the currently active LED
int previousClosestValue = -1; // Variable to store the previous closest value
const int buffer = 10; // Buffer around each value
bool prevBlinkState = false; // Previous blink state to detect threshold crossing
void readAnalogSignal() {
delay(2);
int newValue = analogRead(analogPin1); // Read new signal value
// Check if the new value is within the buffer of any existing value
for (int i = 0; i < numLights; i++) {
if (abs(newValue - signalValuesByAge[i]) <= buffer) {
return; // Do not update the array if within the buffer range
}
}
insertAndSort(newValue); // Insert the new value into both arrays
}
void insertAndSort(int value) {
// Insert new value into the array sorted by age
signalValuesByAge[currentIndex] = value;
currentIndex = (currentIndex + 1) % numLights; // Update the index to the oldest value
// Copy values from age array to magnitude array
for (int i = 0; i < numLights; i++) {
signalValuesByMagnitude[i] = signalValuesByAge[i];
}
// Sort the magnitude array
for (int i = 0; i < numLights - 1; i++) {
for (int j = i + 1; j < numLights; j++) {
if (signalValuesByMagnitude[i] < signalValuesByMagnitude[j]) {
int temp = signalValuesByMagnitude[i];
signalValuesByMagnitude[i] = signalValuesByMagnitude[j];
signalValuesByMagnitude[j] = temp;
}
}
}
for (int i = 0; i < numLights; i++) {
Serial.print(signalValuesByMagnitude[i]);// for some reason this shows only three numbers? and when a 4th shows up it breaks...
Serial.print(" ");
}
Serial.println();
}
void updateLEDs() {
int currentValue = analogRead(analogPin1); // Read the current signal value
int closestIndex = 0;
int smallestDifference = abs(currentValue - signalValuesByMagnitude[0]);
// Find the closest value within the buffer range
for (int i = 1; i < numLights; i++) {
int difference = abs(currentValue - signalValuesByMagnitude[i]);
if (difference < smallestDifference) {
smallestDifference = difference;
closestIndex = i;
}
}
// If the closest value within the buffer range is the same as the previous closest value, keep the current active LED
if (abs(previousClosestValue - signalValuesByMagnitude[closestIndex]) <= buffer) {
closestIndex = currentActiveLED;
} else {
previousClosestValue = signalValuesByMagnitude[closestIndex];
}
// Update LEDs to light only the closest one
for (int i = 0; i < numLights; i++) {
if (i == closestIndex) {
digitalWrite(ledPins[i], HIGH); // Turn on the closest matching LED
currentActiveLED = i; // Update the currently active LED
} else {
digitalWrite(ledPins[i], LOW); // Turn off all other LEDs
}
}
}
void controlReadingAndBlinking() {
int blinkSignal = analogRead(analogPin2);
bool blinkState = (blinkSignal > 512); // Simple threshold for blinking
// Check for threshold crossing
if (blinkState && !prevBlinkState) {
readAnalogSignal(); // Read and add new data only once when crossing the threshold
}
prevBlinkState = blinkState; // Update the previous blink state
// Blink the active LED
for (int i = 0; i < numLights; i++) {
if (digitalRead(ledPins[i]) == HIGH) {
digitalWrite(ledPins[i], blinkState ? HIGH : LOW); // Blink the active LED
}
}
}
void setup() {
Serial.begin(250000);
for (int i = 0; i < numLights; i++) {
pinMode(ledPins[i], OUTPUT);
}
pinMode(analogPin1, INPUT);
pinMode(analogPin2, INPUT);
}
void loop() {
updateLEDs();
controlReadingAndBlinking();
}

