Skip to content

Instantly share code, notes, and snippets.

@SeanMcTex
Created April 25, 2023 14:32
Show Gist options
  • Save SeanMcTex/3aa83ca059b0f9b98bd299960e1cc5df to your computer and use it in GitHub Desktop.
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.
/* 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