Created
April 25, 2023 14:32
-
-
Save SeanMcTex/3aa83ca059b0f9b98bd299960e1cc5df to your computer and use it in GitHub Desktop.
Code for sound-reactive bodhran (Irish drum), as per article at http://www.mcmains.net.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Sound-Reactive Drums for Adafruit Gemma/Trinket and NeoPixel LEDs. | |
Hardware requirements: | |
- Adafruit Gemma, Gemma M0, or 3V Trinket microcontroller. | |
Do NOT use a 5V Trinket; circuit DEPENDS on the 3V regulator! | |
- Adafruit Electret Microphone Amplifier (product ID: 1063) | |
- A length of NeoPixel LEDs, such as: | |
o Adafruit Flora RGB Smart Pixels (ID: 1260) | |
o Adafruit NeoPixel Digital LED strip (ID: 1138) | |
o Adafruit Neopixel Ring (ID: 1463) | |
- Lithium-polymer battery (such as 1578, but other sizes OK) and charger (1304) | |
Software requirements: | |
- Adafruit NeoPixel library | |
Connections: | |
- 3Vo (3V) to mic amp + | |
- Vout (5V) to NeoPixel + | |
- GND to mic amp - and NeoPixel - | |
- Mic OUT to analog pin (configurable below) | |
- Neopixel IN to digital pin (configurable below) | |
Disconnect USB after programming; audio input requires clean DC from battery. | |
Written by Adafruit Industries. Distributed under the BSD license. | |
This paragraph must be included in any redistribution. | |
*/ | |
#include <Adafruit_NeoPixel.h> | |
#include <Adafruit_DotStar.h> | |
#include "Adafruit_FreeTouch.h" | |
#define LED_PIN 0 // NeoPixel LED strand is connected to D0 | |
#define NUM_PIXELS 60 // Number of NeoPixels in strand | |
#define MIC_PIN A1 // Microphone connects to A1 (aka D2) | |
#define DC_OFFSET 0 // DC offset in mic signal - if unusure, leave 0 | |
#define NOISE 90 // Noise floor in mic signal | |
#define SAMPLES 50 // Length of buffer for dynamic level adjustment | |
#define BRIGHTNESS 255 // 0 (off) to 255 (max brightness) | |
#define FLOOR 20 // Minimum magnitude necessary to trigger strip | |
#define TOUCH_PIN A0 // Analog pin to use for capacative switch | |
const uint8_t PROGMEM gamma8[] = { | |
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | |
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, | |
1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, | |
2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, | |
5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, | |
10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, | |
17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25, | |
25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36, | |
37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50, | |
51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68, | |
69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89, | |
90, 92, 93, 95, 96, 98, 99, 101, 102, 104, 105, 107, 109, 110, 112, 114, | |
115, 117, 119, 120, 122, 124, 126, 127, 129, 131, 133, 135, 137, 138, 140, 142, | |
144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 167, 169, 171, 173, 175, | |
177, 180, 182, 184, 186, 189, 191, 193, 196, 198, 200, 203, 205, 208, 210, 213, | |
215, 218, 220, 223, 225, 228, 231, 233, 236, 239, 241, 244, 247, 249, 252, 255 | |
}; | |
int lvl[SAMPLES]; // Save up recent mic readings | |
int damp = 10; // Current "dampened" audio level | |
int minLvlAvg = 0; // For dynamic adjustment of graph low & high | |
int maxLvlAvg = 512; | |
byte counter = 0; // Array index for storing recent mic readings | |
enum modes { | |
allGreen, | |
rainbow, | |
irishFlag | |
}; | |
modes mode = allGreen; | |
bool switchJustActivated = false; | |
Adafruit_NeoPixel strip(NUM_PIXELS, LED_PIN, NEO_GRB + NEO_KHZ800); | |
// Everything is defined in the Board Support Package | |
// DOTSTAR_NUM number of onboard DotStars (typically just 1) | |
// PIN_DOTSTAR_DATA onboard DotStar data pin | |
// PIN_DOTSTAR_CLK onboard DotStar clock pin | |
Adafruit_DotStar onboardPixel(DOTSTAR_NUM, PIN_DOTSTAR_DATA, PIN_DOTSTAR_CLK, DOTSTAR_BGR); | |
// set up capacitive touch button using the FreeTouch library | |
Adafruit_FreeTouch touchButton = Adafruit_FreeTouch(TOUCH_PIN, OVERSAMPLE_4, RESISTOR_50K, FREQ_MODE_NONE); | |
// Calibrating your capacitive touch sensitivity: Change this variable to something between your capacitive touch serial readouts for on and off | |
int touch = 650; | |
long oldState = 0; | |
void setup() { | |
memset(lvl, 0, sizeof lvl); // Clear lvl[] array to 0 | |
strip.begin(); // Initialize NeoPixels | |
onboardPixel.begin(); // Initialize pins for output | |
onboardPixel.setBrightness(20); | |
onboardPixel.show(); // Turn all LEDs off ASAP | |
touchButton.begin(); | |
} | |
void setPixelWithGamma(int i, int r, int g, int b) { | |
strip.setPixelColor( | |
i, | |
pgm_read_byte(&gamma8[r]), | |
pgm_read_byte(&gamma8[g]), | |
pgm_read_byte(&gamma8[b])); | |
} | |
void loop() { | |
int n = analogRead(MIC_PIN); // Raw reading from mic | |
n = abs(n - 512 - DC_OFFSET); // Center on zero, rectify to 0-512 | |
n = (n <= NOISE) ? 0 : (n - NOISE); // Remove noise/hum | |
lvl[counter] = n; // Save value for dynamic leveling | |
if (++counter >= SAMPLES) counter = 0; // Increment array index, roll over if needed | |
damp = ((damp * 7) + n + 3) / 8; // "Dampened" mic reading (else looks twitchy) | |
// Calculate upper/lower range of prior mic readings | |
uint16_t minLvl = lvl[0], maxLvl = lvl[0]; | |
for (uint8_t i = 1; i < SAMPLES; i++) { | |
if (lvl[i] < minLvl) minLvl = lvl[i]; | |
else if (lvl[i] > maxLvl) maxLvl = lvl[i]; | |
} | |
// minLvl and maxLvl indicate the mic range over prior readings, used | |
// for vertically scaling the output graph (so it looks interesting | |
// regardless of current volume level). If they're too close together | |
// though (e.g. at very low volume levels) the graph becomes super | |
// coarse and 'jumpy'...so keep some minimum distance between them | |
// (this also lets the graph go to zero when no sound is playing): | |
if ((maxLvl - minLvl) < NUM_PIXELS) maxLvl = minLvl + NUM_PIXELS; | |
minLvlAvg = (minLvlAvg * 63 + minLvl + 31) / 64; // Dampen min/max levels | |
maxLvlAvg = (maxLvlAvg * 63 + maxLvl + 31) / 64; // (fake rolling average) | |
// Display magnitude based on dampened reading scaled by dynamic min/max levels: | |
int magnitude = BRIGHTNESS * (damp - minLvlAvg) / (long)(maxLvlAvg - minLvlAvg); | |
if (magnitude < FLOOR) magnitude = 0; // Clip to strand bounds | |
else if (magnitude > BRIGHTNESS) magnitude = BRIGHTNESS; | |
// Color NeoPixels based on above calculations & apply rainbow gradient. | |
strip.fill(0); // Clear everything, easier than setting individual pixels | |
for (uint8_t i = 0; i < NUM_PIXELS; i++) { // For each pixel | |
switch (mode) { | |
case rainbow: | |
// Use the ColorHSV function to produce a rainbow of hues. 20000 is | |
// green-ish, the -40000 proceeds "backwards" through the color wheel, | |
// with red at 0 and a magenta-ish hue around -20000. This is explained | |
// in more depth on the "Arduino Library Use" page of NeoPixel Uberguide. | |
strip.setPixelColor(i, strip.ColorHSV(20000 - 40000 * i / strip.numPixels(), 255, magnitude)); | |
break; | |
case allGreen: | |
strip.setPixelColor(i, 0, magnitude, 0); | |
break; | |
case irishFlag: | |
switch ((i / (NUM_PIXELS / 3) + millis() / 1000) % 3) { | |
case 0: | |
setPixelWithGamma(i, 255, 127, 0); | |
break; | |
case 1: | |
setPixelWithGamma(i, 255, 255, 255); | |
break; | |
case 2: | |
setPixelWithGamma(i, 0, 255, 0); | |
break; | |
} | |
break; | |
} | |
} | |
strip.show(); // Update NeoPixels | |
// Handle touch switch | |
long newState = touchButton.measure(); | |
if (newState > touch && oldState < touch) { | |
// Short delay to debounce button. | |
delay(200); | |
// Check if button is still low after debounce. | |
long newState = touchButton.measure(); | |
switchJustActivated = (newState > touch); | |
} else { | |
switchJustActivated = false; | |
} | |
oldState = newState; | |
if (switchJustActivated) { | |
switch (mode) { | |
case allGreen: | |
mode = rainbow; | |
break; | |
case rainbow: | |
mode = irishFlag; | |
break; | |
case irishFlag: | |
mode = allGreen; | |
break; | |
} | |
} | |
// Show current mode on built-in LED | |
switch (mode) { | |
case allGreen: | |
onboardPixel.setPixelColor(0, 0, 255, 0); | |
break; | |
case rainbow: | |
onboardPixel.setPixelColor(0, 0, 0, 255); | |
break; | |
case irishFlag: | |
onboardPixel.setPixelColor(0, 255, 127, 0); | |
break; | |
} | |
onboardPixel.show(); //Update with new color | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment