Last active
July 26, 2023 15:21
-
-
Save atuline/150182adac84edaf22d1d15cba6b1fb5 to your computer and use it in GitHub Desktop.
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
/* fft_log_music | |
* | |
* By: Andrew Tuline | |
* | |
* Date: Mar, 2019 | |
* | |
* | |
* Why use a 7 band MSGEQ7 graphic equalizer chip when you can get many times more frequency bins with software alone and on an Arduino UNO to boot? | |
* | |
* This example combines high speed A/D conversion, FFT (Fast Fourier Transform) pitch detection combined with simple FastLED library display using | |
* an Arduino Nano, an ADMP401 MEMS microphone and a strip of APA102 led's. | |
* | |
* | |
* The advantage of using MSGEQ7 chips is that the coding is relatively simple. The disadvantage is that: | |
* | |
* - you need extra hardware. | |
* - you get a measly 7 frequency bins. | |
* - the results can often be jittery. | |
* | |
* By using the Open Music Labs FFT (Fast Fourier Transform) library in conjunction with high speed A/D conversion, you can perform pitch detection on an | |
* Arduino UNO with much more capability than an MSGEQ7 chip. Oh, and that library gives you about a 9 millisecond conversion, and combined with FastLED | |
* you can get on the order of 95 frames per second. That's no slouch for an 8 bit 16MHz microcontroller. | |
* | |
* The FFT library is available at http://wiki.openmusiclabs.com/wiki/ArduinoFFT | |
* | |
* The A/D conversion uses direct port manipulation and can run at 19KHz, 38KHz or even higher as opposed to 8KHz for a standard analogRead() conversion. | |
* Furthermore, by disabling interrupts for a short period of time, you can reduce jitter during the sampling. This has a side effect of making 3 pin WS2812 | |
* displays less reliable and, as a result, we'll use 4 pin APA102 LED's instead. Reference: | |
* | |
* https://github.com/FastLED/FastLED/wiki/Interrupt-problems | |
* | |
* | |
* The microphone used for this routine is a Sparkfun ADMP401 MEMS microphone, which runs at 3.3V. With this microphone, we need to tie the AREF pin to 3.3V, | |
* and in setup(), perform: | |
* | |
* analogReference(EXTERNAL); | |
* | |
* or use the direct port equivalent, which is: | |
* | |
* ADMUX |= (0 << REFS0); | |
* | |
* | |
* Testing has been performed with a an Android based Function Generator by Keuwlsoft. With so many frequency bins to choose from, you can select the ranges you | |
* want to work with and discard the rest. | |
* | |
*/ | |
#define qsubd(x, b) ((x>b)?b:0) // A digital unsigned subtraction macro. if result <0, then => 0. Otherwise, take on fixed value. | |
#define qsuba(x, b) ((x>b)?x-b:0) // Unsigned subtraction macro. if result <0, then => 0. | |
// FFT Definitions | |
#define LOG_OUT 1 // Use logarithmic based bins (is required for the library to run). | |
#define FFT_N 256 // Set to 256 point fft. Any less, and the upper ranges won't work well. | |
#define DC_OFFSET 509 // DC offset in mic signal. Should probably be about 512. | |
#define MIC_PIN 5 // We're using A5. | |
#include <FFT.h> // FFT library at http://wiki.openmusiclabs.com/wiki/ArduinoFFT | |
#include "FastLED.h" // FastLED library at https://github.com/FastLED/FastLED | |
#if FASTLED_VERSION < 3001000 | |
#error "Requires FastLED 3.1 or later; check github for latest code." | |
#endif | |
// Fixed definitions cannot change on the fly. | |
#define LED_DT 12 // Data pin to connect to the strip. | |
#define LED_CK 11 // Clock pin for APA102 or WS2801 | |
#define COLOR_ORDER BGR // It's GRB for WS2812B | |
#define LED_TYPE APA102 // What kind of strip are you using (APA102, WS2801 or WS2812B) | |
#define NUM_LEDS 40 // Number of LED's. | |
// Initialize changeable global variables. | |
uint8_t max_bright = 128; // Overall brightness definition. It can be changed on the fly. | |
struct CRGB leds[NUM_LEDS]; // Initialize our LED array. | |
void setup() { | |
Serial.begin(57600); // Initialize serial port for debugging. | |
delay(1000); // Soft startup to ease the flow of electrons. | |
LEDS.addLeds<LED_TYPE, LED_DT, LED_CK, COLOR_ORDER>(leds, NUM_LEDS); // Use this for WS2801 or APA102 | |
// LEDS.addLeds<LED_TYPE, LED_DT, COLOR_ORDER>(leds, NUM_LEDS); // Use this for WS2812 | |
FastLED.setBrightness(max_bright); | |
set_max_power_in_volts_and_milliamps(5, 500); // FastLED Power management set at 5V, 500mA. | |
// Setup the ADC for polled 10 bit sampling on analog pin 5 at 19.2kHz. | |
cli(); // Disable interrupts. | |
ADCSRA = 0; // Clear this register. | |
ADCSRB = 0; // Ditto. | |
ADMUX = 0; // Ditto. | |
ADMUX |= (MIC_PIN & 0x07); // Set A5 analog input pin. | |
ADMUX |= (0 << REFS0); // Set reference voltage (analog reference(external), or using 3.3V microphone on 5V Arduino. | |
// Set that to 1 if using 5V microphone or 3.3V Arduino. | |
// ADMUX |= (1 << ADLAR); // Left justify to get 8 bits of data. | |
ADMUX |= (0 << ADLAR); // Right justify to get full 10 A/D bits. | |
// ADCSRA |= bit (ADPS0) | bit (ADPS2); // 32 scaling or 38.5 KHz sampling | |
// ADCSRA |= bit (ADPS1) | bit (ADPS2); // Set ADC clock with 64 prescaler where 16mHz/64=250kHz and 250khz/13 instruction cycles = 19.2khz sampling. | |
ADCSRA |= bit (ADPS0) | bit (ADPS1) | bit (ADPS2); // 128 prescaler with 9.6 KHz sampling | |
ADCSRA |= (1 << ADATE); // Enable auto trigger. | |
// ADCSRA |= (1 << ADIE); // Enable interrupts when measurement complete (if using ISR method). Sorry, we're using polling here. | |
ADCSRA |= (1 << ADEN); // Enable ADC. | |
ADCSRA |= (1 << ADSC); // Start ADC measurements. | |
sei(); // Re-enable interrupts. | |
} // setup() | |
void loop() { | |
showfps(); // Debug output of how many frames per second we're getting. Comment this out in production. | |
getFFT(); // Let's take FFT_N samples and crunch 'em. | |
fftDisplay(); // Let's calculate the LED display from our FFT output array. | |
FastLED.show(); // And then display it. | |
} // loop() | |
void getFFT() { | |
get_sound(); // High speed sound sampling. | |
fft_window(); // Window the data for better frequency response. | |
fft_reorder(); // Reorder the data before doing the fft. | |
fft_run(); // Process the data in the fft. | |
fft_mag_log(); // I guess we'll be converting to logarithm. | |
// Really, I don't know what these do to tell you the truth. | |
} // GetFFT() | |
void get_sound() { // Uses high speed polled analog sampling and NOT analogRead(). | |
cli(); | |
for (int i = 0 ; i < FFT_N*2 ; i+=2) { // Save 256 samples. No more, no less. | |
while(!(ADCSRA & 0x10)); // Wait for adc to be ready. | |
ADCSRA = 0xf5; // restart adc | |
fft_input[i] = ADC - DC_OFFSET; // Get the full 10 bit A/D conversion and center it. | |
fft_input[i+1] = 0; | |
/* Serial.print(abs(sample)); // Serial plot graph of our sampling. | |
Serial.print(" "); // Lowest and highest values are graphed so that the plot isn't auto-scaled. | |
Serial.print(0); // Lowest value | |
Serial.print(" "); | |
Serial.print(512); // Highest value | |
Serial.println(" "); | |
*/ | |
} | |
sei(); | |
} // get_sound() | |
void fftDisplay() { | |
#define hueinc 0 // A hue increment value to make it rotate a bit. | |
#define micmult 10 // Bin values are very low, to let's crank 'em up. | |
#define noiseval 32 // Increase this to reduce sensitivity. | |
for (int i= 0; i < NUM_LEDS; i++) { // Run through the LED array. | |
int tmp = qsuba(fft_log_out[2*i+2], noiseval); // Get the sample and subtract the 'quiet' normalized values, but don't go < 0. | |
if (tmp > (leds[i].r + leds[i].g + leds[i].b)) // Refresh an LED only when the intensity is low. By Andrew Tuline. | |
leds[i] = CHSV(tmp*micmult+hueinc, 255, tmp*micmult); // Note how we really cranked up the tmp value to get BRIGHT LED's. Also increment the hue for fun. | |
leds[i].nscale8(224); // Let's fade the whole thing over time as well. | |
} | |
} // fftDisplay() | |
void showfps() { | |
long currentMillis = 0; | |
static long lastMillis = 0; | |
static long loops = 0; | |
currentMillis=millis(); // Determine frames per second | |
loops++; | |
if(currentMillis - lastMillis > 1000){ | |
Serial.println(loops); | |
lastMillis = currentMillis; | |
loops = 0; | |
} | |
} // showfps() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment