diff --git a/3D_Printed_LED_Microphone_Flag/3D_Printed_LED_Microphone_Flag.ino b/3D_Printed_LED_Microphone_Flag/3D_Printed_LED_Microphone_Flag.ino new file mode 100644 index 000000000..fa2958053 --- /dev/null +++ b/3D_Printed_LED_Microphone_Flag/3D_Printed_LED_Microphone_Flag.ino @@ -0,0 +1,232 @@ +/* +LED VU meter for Arduino and Adafruit NeoPixel LEDs. + + Hardware requirements: + - Most Arduino or Arduino-compatible boards (ATmega 328P or better). + - Adafruit Electret Microphone Amplifier (ID: 1063) + - Adafruit Flora RGB Smart Pixels (ID: 1260) + OR + - Adafruit NeoPixel Digital LED strip (ID: 1138) + - Optional: battery for portable use (else power through USB or adapter) + Software requirements: + - Adafruit NeoPixel library + + Connections: + - 3.3V to mic amp + + - GND to mic amp - + - Analog pin to microphone output (configurable below) + - Digital pin to LED data input (configurable below) + See notes in setup() regarding 5V vs. 3.3V boards - there may be an + extra connection to make and one line of code to enable or disable. + + Written by Adafruit Industries. Distributed under the BSD license. + This paragraph must be included in any redistribution. + + fscale function: + Floating Point Autoscale Function V0.1 + Written by Paul Badger 2007 + Modified from code by Greg Shakar + + */ + +#include +#include + +#define N_PIXELS 16 // Number of pixels in strand +#define MIC_PIN A1 // Microphone is attached to this analog pin +#define LED_PIN 1 // NeoPixel LED strand is connected to this pin +#define SAMPLE_WINDOW 10 // Sample window for average level +#define PEAK_HANG 24 //Time of pause before peak dot falls +#define PEAK_FALL 4 //Rate of falling peak dot +#define INPUT_FLOOR 10 //Lower range of analogRead input +#define INPUT_CEILING 300 //Max range of analogRead input, the lower the value the more sensitive (1023 = max) + + + +byte peak = 16; // Peak level of column; used for falling dots +unsigned int sample; + +byte dotCount = 0; //Frame counter for peak dot +byte dotHangCount = 0; //Frame counter for holding peak dot + +Adafruit_NeoPixel strip = Adafruit_NeoPixel(N_PIXELS, LED_PIN, NEO_GRB + NEO_KHZ800); + +void setup() +{ + // This is only needed on 5V Arduinos (Uno, Leonardo, etc.). + // Connect 3.3V to mic AND TO AREF ON ARDUINO and enable this + // line. Audio samples are 'cleaner' at 3.3V. + // COMMENT OUT THIS LINE FOR 3.3V ARDUINOS (FLORA, ETC.): + // analogReference(EXTERNAL); + + // Serial.begin(9600); + strip.begin(); + strip.show(); // Initialize all pixels to 'off' + +} + +void loop() +{ + unsigned long startMillis= millis(); // Start of sample window + float peakToPeak = 0; // peak-to-peak level + + unsigned int signalMax = 0; + unsigned int signalMin = 1023; + unsigned int c, y; + + + // collect data for length of sample window (in mS) + while (millis() - startMillis < SAMPLE_WINDOW) + { + sample = analogRead(MIC_PIN); + if (sample < 1024) // toss out spurious readings + { + if (sample > signalMax) + { + signalMax = sample; // save just the max levels + } + else if (sample < signalMin) + { + signalMin = sample; // save just the min levels + } + } + } + peakToPeak = signalMax - signalMin; // max - min = peak-peak amplitude + + // Serial.println(peakToPeak); + + + //Fill the strip with rainbow gradient + for (int i=0;i<=strip.numPixels()-1;i++){ + strip.setPixelColor(i,Wheel(map(i,0,strip.numPixels()-1,30,150))); + } + + + //Scale the input logarithmically instead of linearly + c = fscale(INPUT_FLOOR, INPUT_CEILING, strip.numPixels(), 0, peakToPeak, 2); + + + + + if(c < peak) { + peak = c; // Keep dot on top + dotHangCount = 0; // make the dot hang before falling + } + if (c <= strip.numPixels()) { // Fill partial column with off pixels + drawLine(strip.numPixels(), strip.numPixels()-c, strip.Color(0, 0, 0)); + } + + // Set the peak dot to match the rainbow gradient + y = strip.numPixels() - peak; + + strip.setPixelColor(y-1,Wheel(map(y,0,strip.numPixels()-1,30,150))); + + strip.show(); + + // Frame based peak dot animation + if(dotHangCount > PEAK_HANG) { //Peak pause length + if(++dotCount >= PEAK_FALL) { //Fall rate + peak++; + dotCount = 0; + } + } + else { + dotHangCount++; + } +} + +//Used to draw a line between two points of a given color +void drawLine(uint8_t from, uint8_t to, uint32_t c) { + uint8_t fromTemp; + if (from > to) { + fromTemp = from; + from = to; + to = fromTemp; + } + for(int i=from; i<=to; i++){ + strip.setPixelColor(i, c); + } +} + + +float fscale( float originalMin, float originalMax, float newBegin, float +newEnd, float inputValue, float curve){ + + float OriginalRange = 0; + float NewRange = 0; + float zeroRefCurVal = 0; + float normalizedCurVal = 0; + float rangedValue = 0; + boolean invFlag = 0; + + + // condition curve parameter + // limit range + + if (curve > 10) curve = 10; + if (curve < -10) curve = -10; + + curve = (curve * -.1) ; // - invert and scale - this seems more intuitive - postive numbers give more weight to high end on output + curve = pow(10, curve); // convert linear scale into lograthimic exponent for other pow function + + /* + Serial.println(curve * 100, DEC); // multply by 100 to preserve resolution + Serial.println(); + */ + + // Check for out of range inputValues + if (inputValue < originalMin) { + inputValue = originalMin; + } + if (inputValue > originalMax) { + inputValue = originalMax; + } + + // Zero Refference the values + OriginalRange = originalMax - originalMin; + + if (newEnd > newBegin){ + NewRange = newEnd - newBegin; + } + else + { + NewRange = newBegin - newEnd; + invFlag = 1; + } + + zeroRefCurVal = inputValue - originalMin; + normalizedCurVal = zeroRefCurVal / OriginalRange; // normalize to 0 - 1 float + + // Check for originalMin > originalMax - the math for all other cases i.e. negative numbers seems to work out fine + if (originalMin > originalMax ) { + return 0; + } + + if (invFlag == 0){ + rangedValue = (pow(normalizedCurVal, curve) * NewRange) + newBegin; + + } + else // invert the ranges + { + rangedValue = newBegin - (pow(normalizedCurVal, curve) * NewRange); + } + + return rangedValue; +} + + +// Input a value 0 to 255 to get a color value. +// The colours are a transition r - g - b - back to r. +uint32_t Wheel(byte WheelPos) { + if(WheelPos < 85) { + return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0); + } + else if(WheelPos < 170) { + WheelPos -= 85; + return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3); + } + else { + WheelPos -= 170; + return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3); + } +} diff --git a/3D_Printed_LED_Microphone_Flag/3D_Printed_LED_Microphone_Flag.py b/3D_Printed_LED_Microphone_Flag/3D_Printed_LED_Microphone_Flag.py new file mode 100644 index 000000000..c594fd273 --- /dev/null +++ b/3D_Printed_LED_Microphone_Flag/3D_Printed_LED_Microphone_Flag.py @@ -0,0 +1,192 @@ +# LED VU meter for Arduino and Adafruit NeoPixel LEDs. + +# Hardware requirements: +# - M0 boards +# - Adafruit Electret Microphone Amplifier (ID: 1063) +# - Adafruit Flora RGB Smart Pixels (ID: 1260) +# OR +# - Adafruit NeoPixel Digital LED strip (ID: 1138) +# - Optional: battery for portable use (else power through USB or adapter) +# Software requirements: +# - Adafruit NeoPixel library + +# Connections: +# - 3.3V to mic amp + +# - GND to mic amp - +# - Analog pin to microphone output (configurable below) +# - Digital pin to LED data input (configurable below) +# See notes in setup() regarding 5V vs. 3.3V boards - there may be an +# extra connection to make and one line of code to enable or disable. + +# Written by Adafruit Industries. Distributed under the BSD license. +# This paragraph must be included in any redistribution. + +# fscale function: +# Floating Point Autoscale Function V0.1 +# Written by Paul Badger 2007 +# Modified fromhere code by Greg Shakar +# Ported to Circuit Python by Mikey Sklar + +import board +import neopixel +import time +from analogio import AnalogIn +import array + +n_pixels = 16 # Number of pixels you are using +mic_pin = AnalogIn(board.A1) # Microphone is attached to this analog pin +led_pin = board.D1 # NeoPixel LED strand is connected to this pin +sample_window = .1 # Sample window for average level +peak_hang = 24 # Time of pause before peak dot falls +peak_fall = 4 # Rate of falling peak dot +input_floor = 10 # Lower range of analogRead input +input_ceiling = 300 # Max range of analogRead input, the lower the value the more sensitive (1023 = max) + +peak = 16 # Peak level of column; used for falling dots +sample = 0 + +dotcount = 0 # Frame counter for peak dot +dothangcount = 0 # Frame counter for holding peak dot + +strip = neopixel.NeoPixel(led_pin, n_pixels, brightness=1, auto_write=False) + +def wheel(pos): + # Input a value 0 to 255 to get a color value. + # The colours are a transition r - g - b - back to r. + if (pos < 0) or (pos > 255): + return (0, 0, 0) + if (pos < 85): + return (int(pos * 3), int(255 - (pos*3)), 0) + elif (pos < 170): + pos -= 85 + return (int(255 - pos*3), 0, int(pos*3)) + else: + pos -= 170 + return (0, int(pos*3), int(255 - pos*3)) + +def remapRange(value, leftMin, leftMax, rightMin, rightMax): + # this remaps a value fromhere original (left) range to new (right) range + # Figure out how 'wide' each range is + leftSpan = leftMax - leftMin + rightSpan = rightMax - rightMin + + # Convert the left range into a 0-1 range (int) + valueScaled = int(value - leftMin) / int(leftSpan) + + # Convert the 0-1 range into a value in the right range. + return int(rightMin + (valueScaled * rightSpan)) + +def fscale(originalmin, originalmax, newbegin, newend, inputvalue, curve): + originalrange = 0 + newrange = 0 + zerorefcurval = 0 + normalizedcurval = 0 + rangedvalue = 0 + invflag = 0 + + # condition curve parameter + # limit range + if (curve > 10): + curve = 10 + if (curve < -10): + curve = -10 + + # - invert and scale - + # this seems more intuitive + # postive numbers give more weight to high end on output + curve = (curve * -.1) + curve = pow(10, curve) # convert linear scale into lograthimic exponent for other pow function + + # Check for out of range inputValues + if (inputvalue < originalmin): + inputvalue = originalmin + + if (inputvalue > originalmax): + inputvalue = originalmax + + # Zero Refference the values + originalrange = originalmax - originalmin + + if (newend > newbegin): + newrange = newend - newbegin + else: + newrange = newbegin - newend + invflag = 1 + + zerorefcurval = inputvalue - originalmin + normalizedcurval = zerorefcurval / originalrange # normalize to 0 - 1 float + + # Check for originalMin > originalMax + # -the math for all other cases + # i.e. negative numbers seems to work out fine + if (originalmin > originalmax ): + return(0) + + if (invflag == 0): + rangedvalue = (pow(normalizedcurval, curve) * newrange) + newbegin + else: # invert the ranges + rangedvalue = newbegin - (pow(normalizedcurval, curve) * newrange); + + return(rangedvalue) + +def drawLine(fromhere, to): + + fromheretemp = 0 + + if (fromhere > to): + fromheretemp = fromhere + fromhere = to + to = fromheretemp + + for i in range(fromhere, to): + strip[i] = (0,0,0) + +while True: + + time_start = time.monotonic() # current time used for sample window + peaktopeak = 0 # peak-to-peak level + signalmax = 0 + signalmin = 1023 + c = 0 + y = 0 + + # collect data for length of sample window (in seconds) + while ( ( time.monotonic() - time_start ) < sample_window): + + sample = mic_pin.value / 64 # convert to arduino 10-bit [1024] fromhere 16-bit [65536] + + if (sample < 1024): # toss out spurious readings + + if (sample > signalmax): + signalmax = sample # save just the max levels + elif (sample < signalmin): + signalmin = sample # save just the min levels + + peaktopeak = signalmax - signalmin # max - min = peak-peak amplitude + + # Fill the strip with rainbow gradient + for i in range(0, len(strip)): + strip[i] = wheel(remapRange(i, 0, (n_pixels - 1), 30, 150)) + + # Scale the input logarithmically instead of linearly + c = fscale(input_floor, input_ceiling, (n_pixels - 1), 0, peaktopeak, 2) + + if (c < peak): + peak = c # keep dot on top + dothangcount = 0 # make the dot hang before falling + + if (c <= n_pixels): # fill partial column with off pixels + drawLine(n_pixels, n_pixels - int(c)) + + # Set the peak dot to match the rainbow gradient + y = n_pixels - peak + strip.fill = (y - 1, wheel(remapRange(y, 0, (n_pixels - 1), 30, 150))) + strip.write() + + # Frame based peak dot animation + if(dothangcount > peak_hang): # Peak pause length + if(++dotcount >= peak_fall): # Fall rate + peak += 1 + dotcount = 0 + else: + dothangcount += 1 diff --git a/3D_Printed_LED_Microphone_Flag/README.md b/3D_Printed_LED_Microphone_Flag/README.md new file mode 100644 index 000000000..a1f981b95 --- /dev/null +++ b/3D_Printed_LED_Microphone_Flag/README.md @@ -0,0 +1,4 @@ +# 3D_Printed_LED_Microphone_Flag + +Code to accompany this tutorial: +https://learn.adafruit.com/3d-printed-led-microphone-flag