Hey everybody, I am working on this code and need some honest review from you guys. Thanks in advance!
Code:
/*
* TeensyDrum v0.8 - Snare: Head + Rim
*
* v0.8: Retrigger detection in aftershock.
* If signal jumps by retriggerThresh in one read = new hit,
* exit aftershock immediately so IDLE catches it.
*/
// ======================== ACTIVE PADS =================
//#define PAD_KICK // A0
#define PAD_SNARE // A1=head
#define PAD_SNARE_RIM // A2=rim
// ======================== MIDI ========================
const int channel = 10;
// ======================== SNARE =======================
#ifdef PAD_SNARE
const int snareNoteHead = 38;
const int snareNoteRim = 37;
const int snarePinHead = A1;
const int snarePinRim = A2;
int headThreshold = 40;
int headSensitivity = 920;
float headExponent = 1.8;
int headPeakDrops = 1;
unsigned int headScanMs = 3;
unsigned int headAfterMs = 2;
int headRetrigger = 100; // sudden rise to detect new hit in aftershock
int rimThreshold = 80;
int rimSensitivity = 500;
float rimExponent = 1.5;
int rimPeakDrops = 1;
unsigned int rimScanMs = 6;
unsigned int rimAfterMs = 6;
int rimRetrigger = 80;
// Zone detection
float rimRatio = 0.3;
float rimRatioTimingBoost = 0.15;
uint8_t headLUT[128];
uint8_t rimLUT[128];
#endif
// ======================== KICK ========================
#ifdef PAD_KICK
const int kickNote = 36;
const int kickPin = A0;
int kickThreshold = 40;
int kickSensitivity = 1100;
float kickExponent = 1.0;
int kickPeakDrops = 1;
unsigned int kickScanMs = 3;
unsigned int kickAfterMs = 0;
int kickRetrigger = 80;
uint8_t kickLUT[128];
#endif
// ===================== VELOCITY LUT ===================
void buildLUT(uint8_t* lut, float exp) {
lut[0] = 0;
for (int i = 1; i < 128; i++) {
float x = (float)i / 127.0;
lut[i] = (uint8_t)constrain(lroundf(pow(x, exp) * 126.0) + 1, 1, 127);
}
}
void buildAllLUTs() {
#ifdef PAD_SNARE
buildLUT(headLUT, headExponent);
buildLUT(rimLUT, rimExponent);
#endif
#ifdef PAD_KICK
buildLUT(kickLUT, kickExponent);
#endif
}
int calcVelocity(int peak, int threshold, int sens, uint8_t* lut) {
if (peak <= threshold) return 1;
if (peak >= sens) return lut[127];
int idx = (int)((float)(peak - threshold) / (float)(sens - threshold) * 127.0);
return lut[constrain(idx, 1, 127)];
}
// ======================== SETUP ========================
void setup() {
Serial.begin(115200);
analogReadResolution(10);
#ifdef PAD_SNARE
pinMode(snarePinHead, INPUT);
pinMode(snarePinRim, INPUT);
#endif
#ifdef PAD_KICK
pinMode(kickPin, INPUT);
#endif
while (!Serial && millis() < 2500) ;
buildAllLUTs();
Serial.println("TeensyDrum v0.8");
}
// ======================== LOOP =========================
void loop() {
#ifdef PAD_SNARE
{
int head = analogRead(snarePinHead);
#ifdef PAD_SNARE_RIM
int rim = analogRead(snarePinRim);
#else
int rim = 0;
#endif
snareDetect(head, rim);
}
#endif
#ifdef PAD_KICK
{
int kick = analogRead(kickPin);
kickDetect(kick);
}
#endif
checkSerial();
while (usbMIDI.read()) {}
}
// ======================== SNARE ========================
#ifdef PAD_SNARE
void snareDetect(int head, int rim) {
static int state;
static int peakHead, peakRim;
static int prevHead, prevRim;
static int drops;
static int lastNote;
static bool isRim;
static int firstTrigger;
static elapsedMillis msec;
switch (state) {
// ---- IDLE ----
case 0: {
bool headRise = (head > headThreshold && prevHead < headThreshold);
bool rimRise = (rim > rimThreshold && prevRim < rimThreshold);
if (headRise || rimRise) {
peakHead = head;
peakRim = rim;
prevHead = head;
prevRim = rim;
drops = 0;
msec = 0;
if (headRise && rimRise) firstTrigger = 3;
else if (rimRise) firstTrigger = 2;
else firstTrigger = 1;
state = 1;
}
prevHead = head;
prevRim = rim;
return;
}
// ---- PEAK TRACKING ----
case 1: {
if (head > peakHead) { peakHead = head; drops = 0; }
if (rim > peakRim) { peakRim = rim; drops = 0; }
int curMax = max(head, rim);
int prevMax = max(prevHead, prevRim);
if (curMax < prevMax) drops++;
else drops = 0;
prevHead = head;
prevRim = rim;
int reqDrops = max(headPeakDrops, rimPeakDrops);
unsigned int timeout = max(headScanMs, rimScanMs);
if (drops >= reqDrops || msec >= timeout) {
float ratio = (peakHead > 0) ? (float)peakRim / (float)peakHead : 100.0;
float ratioNeeded = rimRatio;
if (firstTrigger == 2) ratioNeeded = rimRatioTimingBoost;
if (firstTrigger == 1) ratioNeeded = rimRatio + 0.2;
isRim = (peakRim > rimThreshold && ratio > ratioNeeded);
int note, velocity;
if (isRim) {
note = snareNoteRim;
velocity = calcVelocity(peakRim, rimThreshold, rimSensitivity, rimLUT);
} else {
note = snareNoteHead;
velocity = calcVelocity(peakHead, headThreshold, headSensitivity, headLUT);
}
velocity = constrain(velocity, 1, 127);
usbMIDI.sendNoteOn(note, velocity, channel);
lastNote = note;
msec = 0;
state = 2;
}
return;
}
// ---- AFTERSHOCK with retrigger detection ----
default: {
unsigned int afterMs = isRim ? rimAfterMs : headAfterMs;
int retrig = isRim ? rimRetrigger : headRetrigger;
// New hit during aftershock: sudden rise
int headRise = head - prevHead;
int rimRise = rim - prevRim;
if (headRise > retrig || rimRise > retrig) {
usbMIDI.sendNoteOff(lastNote, 0, channel);
state = 0;
// Don't update prev - let IDLE catch the rising edge
return;
}
prevHead = head;
prevRim = rim;
if (head > headThreshold || rim > rimThreshold) {
msec = 0;
} else if (msec > afterMs) {
usbMIDI.sendNoteOff(lastNote, 0, channel);
state = 0;
}
}
}
}
#endif
// ======================== KICK =========================
#ifdef PAD_KICK
void kickDetect(int voltage) {
static int state;
static int peak;
static int prevVoltage;
static int drops;
static elapsedMillis msec;
switch (state) {
case 0:
if (voltage > kickThreshold && prevVoltage < kickThreshold) {
peak = voltage;
prevVoltage = voltage;
drops = 0;
msec = 0;
state = 1;
}
prevVoltage = voltage;
return;
case 1:
if (voltage > peak) { peak = voltage; drops = 0; }
if (voltage < prevVoltage) drops++;
else drops = 0;
prevVoltage = voltage;
if (drops >= kickPeakDrops || msec >= kickScanMs) {
int velocity = calcVelocity(peak, kickThreshold, kickSensitivity, kickLUT);
velocity = constrain(velocity, 1, 127);
usbMIDI.sendNoteOn(kickNote, velocity, channel);
msec = 0;
state = 2;
}
return;
default: {
int rise = voltage - prevVoltage;
if (rise > kickRetrigger) {
usbMIDI.sendNoteOff(kickNote, 0, channel);
state = 0;
return;
}
prevVoltage = voltage;
if (voltage > kickThreshold) {
msec = 0;
} else if (msec > kickAfterMs) {
usbMIDI.sendNoteOff(kickNote, 0, channel);
state = 0;
}
}
}
}
#endif
// ======================== SERIAL TUNING ========================
void checkSerial() {
if (!Serial.available()) return;
char buf[32];
uint8_t len = 0;
while (Serial.available() && len < 31) {
char c = Serial.read();
if (c == '\n' || c == '\r') break;
buf[len++] = c;
}
buf[len] = 0;
while (Serial.available()) Serial.read();
if (len == 0) return;
char* p = buf;
char cmd[8] = {0};
int ci = 0;
while (*p && *p != ' ' && ci < 7) cmd[ci++] = *p++;
while (*p == ' ') p++;
bool rebuildLUT = false;
#ifdef PAD_SNARE
if (strcmp(cmd,"ht")==0) { headThreshold = atoi(p); Serial.printf("headThreshold = %d\n", headThreshold); }
else if (strcmp(cmd,"hs")==0) { headSensitivity = atoi(p); Serial.printf("headSensitivity = %d\n", headSensitivity); }
else if (strcmp(cmd,"he")==0) { headExponent = atof(p); Serial.printf("headExponent = %.2f\n", headExponent); rebuildLUT = true; }
else if (strcmp(cmd,"hd")==0) { headPeakDrops = atoi(p); Serial.printf("headPeakDrops = %d\n", headPeakDrops); }
else if (strcmp(cmd,"hsc")==0) { headScanMs = atoi(p); Serial.printf("headScanMs = %d\n", headScanMs); }
else if (strcmp(cmd,"ha")==0) { headAfterMs = atoi(p); Serial.printf("headAfterMs = %d\n", headAfterMs); }
else if (strcmp(cmd,"hrt")==0) { headRetrigger = atoi(p); Serial.printf("headRetrigger = %d\n", headRetrigger); }
else if (strcmp(cmd,"rt")==0) { rimThreshold = atoi(p); Serial.printf("rimThreshold = %d\n", rimThreshold); }
else if (strcmp(cmd,"rs")==0) { rimSensitivity = atoi(p); Serial.printf("rimSensitivity = %d\n", rimSensitivity); }
else if (strcmp(cmd,"re")==0) { rimExponent = atof(p); Serial.printf("rimExponent = %.2f\n", rimExponent); rebuildLUT = true; }
else if (strcmp(cmd,"rd")==0) { rimPeakDrops = atoi(p); Serial.printf("rimPeakDrops = %d\n", rimPeakDrops); }
else if (strcmp(cmd,"rsc")==0) { rimScanMs = atoi(p); Serial.printf("rimScanMs = %d\n", rimScanMs); }
else if (strcmp(cmd,"ra")==0) { rimAfterMs = atoi(p); Serial.printf("rimAfterMs = %d\n", rimAfterMs); }
else if (strcmp(cmd,"rrt")==0) { rimRetrigger = atoi(p); Serial.printf("rimRetrigger = %d\n", rimRetrigger); }
else if (strcmp(cmd,"ratio")==0) { rimRatio = atoi(p) / 100.0f; Serial.printf("rimRatio = %.2f\n", rimRatio); }
else if (strcmp(cmd,"boost")==0) { rimRatioTimingBoost = atoi(p) / 100.0f; Serial.printf("rimRatioTimingBoost = %.2f\n", rimRatioTimingBoost); }
else
#endif
#ifdef PAD_KICK
if (strcmp(cmd,"kt")==0) { kickThreshold = atoi(p); Serial.printf("kickThreshold = %d\n", kickThreshold); }
else if (strcmp(cmd,"ks")==0) { kickSensitivity = atoi(p); Serial.printf("kickSensitivity = %d\n", kickSensitivity); }
else if (strcmp(cmd,"ke")==0) { kickExponent = atof(p); Serial.printf("kickExponent = %.2f\n", kickExponent); rebuildLUT = true; }
else if (strcmp(cmd,"kd")==0) { kickPeakDrops = atoi(p); Serial.printf("kickPeakDrops = %d\n", kickPeakDrops); }
else if (strcmp(cmd,"ksc")==0) { kickScanMs = atoi(p); Serial.printf("kickScanMs = %d\n", kickScanMs); }
else if (strcmp(cmd,"ka")==0) { kickAfterMs = atoi(p); Serial.printf("kickAfterMs = %d\n", kickAfterMs); }
else if (strcmp(cmd,"krt")==0) { kickRetrigger = atoi(p); Serial.printf("kickRetrigger = %d\n", kickRetrigger); }
else
#endif
if (strcmp(cmd,"list")==0) {
#ifdef PAD_SNARE
Serial.println("--- SNARE HEAD ---");
Serial.printf(" threshold=%d sensitivity=%d exponent=%.2f\n", headThreshold, headSensitivity, headExponent);
Serial.printf(" peakDrops=%d scanMs=%u afterMs=%u retrigger=%d\n", headPeakDrops, headScanMs, headAfterMs, headRetrigger);
Serial.println("--- SNARE RIM ---");
Serial.printf(" threshold=%d sensitivity=%d exponent=%.2f\n", rimThreshold, rimSensitivity, rimExponent);
Serial.printf(" peakDrops=%d scanMs=%u afterMs=%u retrigger=%d\n", rimPeakDrops, rimScanMs, rimAfterMs, rimRetrigger);
Serial.printf("--- ZONE ---\n rimRatio=%.2f timingBoost=%.2f\n", rimRatio, rimRatioTimingBoost);
Serial.printf("--- NOTES ---\n head=%d rim=%d\n", snareNoteHead, snareNoteRim);
#endif
#ifdef PAD_KICK
Serial.println("--- KICK ---");
Serial.printf(" threshold=%d sensitivity=%d exponent=%.2f\n", kickThreshold, kickSensitivity, kickExponent);
Serial.printf(" peakDrops=%d scanMs=%u afterMs=%u retrigger=%d\n", kickPeakDrops, kickScanMs, kickAfterMs, kickRetrigger);
Serial.printf(" note=%d\n", kickNote);
#endif
}
else Serial.printf("Unknown: %s\n", cmd);
if (rebuildLUT) buildAllLUTs();
}
Last edited: