/******************************************************************************************************************************************************** * * * Project: FFT Spectrum Analyzer * * Target Platform: ESP32 * * * * Version: 1.0 * * Hardware setup: See github * * Spectrum analyses done with analog chips MSGEQ7 * * * * Mark Donners * * The Electronic Engineer * * Website: www.theelectronicengineer.nl * * facebook: https://www.facebook.com/TheelectronicEngineer * * youtube: https://www.youtube.com/channel/UCm5wy-2RoXGjG2F9wpDFF3w * * github: https://github.com/donnersm * * * ********************************************************************************************************************************************************* * Version History * * 1.0 First release, code extraced from 14 band spectrum analyzer 3.00 and modified to by used with FFT on a ESP32. No need for frequency board or * * MCGEQ7 chips. * * - HUB75 interface or * * - WS2812 leds ( matrix/ledstrips) * * - 8/16/32 or 64 channel analyzer * * - calibration for White noise, pink noise, brown noise sensitivity included and selectable * * - Fire screensaver * * - Display of logo and interface text when used with HUB75 * * * ********************************************************************************************************************************************************* * Version FFT 1.0 release July 2021 * ********************************************************************************************************************************************************* * Status | Description * * Open | Some Hub75 displays use a combination of chipsets of are from a different productions batch which will not work with this libary * * Open | Sometime the long press for activating/de-activating the autoChange Pattern mode doesn't work * * Solved | When using 64 bands, band 0 is always at max value. This was caused by the array dize [64]-> solved by chnaging it to 65 * * Not a bug | Different types of HUB75 displays require different libary settings.It is what it is and it all depends on what the distributer sends you.* * | For into on the libary settings, see the library documentation on Github: https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA * * Wish | Web interface. not possible without some heavy workaround cant use WIFI and ADC at same time * * ******************************************************************************************************************************************************* * People who inspired me to do this build and figure out how stuff works: * Dave Plummer https://www.youtube.com/channel/UCNzszbnvQeFzObW0ghk0Ckw * Mrfaptastic https://github.com/mrfaptastic * Scott Marley https://www.youtube.com/user/scottmarley85 * Brian Lough https://www.youtube.com/user/witnessmenow * atomic14 https://www.youtube.com/channel/UC4Otk-uDioJN0tg6s1QO9lw * * Make sure your arduino IDE settings: Compiler warnings is set to default to make sure the code will compile */ #define VERSION "V1.0" #include #include #include "I2SPLUGIN.h" #include #include #include "FFT.h" #include "LEDDRIVER.H" #include "Settings.h" #include "PatternsHUB75.h" #include "PatternsLedstrip.h" #include "fire.h" #include "logos.h" int skip=true; int ButtonOnTimer=0; int ButtonStarttime=0; int ButtonSequenceCounter=0; int ButtonOffTimer=0; int ButtonStoptime=0; int ButtonPressedCounter=0; int ButtonReleasedCounter=0; int ShortPressFlag=0; int LongPressFlag=0; int LongerPressFlag=0; boolean Next_is_new_pressed= true; boolean Next_is_new_release = true; int PreviousPressTime=0; #define up 1 #define down 0 int PeakDirection=0; long LastDoNothingTime = 0; // only needed for screensaver int DemoModeMem=0; // to remember what mode we are in when going to demo, in order to restore it after wake up bool AutoModeMem=false; // same story bool DemoFlag=false; // we need to know if demo mode was manually selected or auto engadged. char LCDPrintBuf[30]; void setup() { Serial.begin(115200); Serial.println("Setting up Audio Input I2S"); setupI2S(); Serial.println("Audio input setup completed"); delay(1000); #ifdef Ledstrip SetupLEDSTRIP(); #endif #ifdef HUB75 SetupHUB75(); if (kMatrixHeight>60){ dma_display->setBrightness8(100); #if LogoBoot drawLogo(); delay(2500); #endif } #endif } void loop() { size_t bytesRead = 0; int TempADC=0; if (skip==false)i2s_adc_disable(I2S_NUM_0); skip=false; // we only want to skip this the very first loop run. //Handle Userinterface { // set brightness and test if button is pressed TempADC = analogRead(BRIGHTNESSPOT); if (TempADC<10){ // ADC value < 10 so button is pressed ButtonOffTimer=0; ButtonOnTimer=millis()-ButtonStarttime; ButtonStoptime=millis(); } else { // no Button pressed so ADC value is not related to button and can be proccessed ButtonOnTimer=0; ButtonOffTimer=millis()-ButtonStoptime; ButtonStarttime = millis(); // read potmeters and process Peakdelay=map(analogRead(PEAKDELAYPOT),0,4095,1,100); BRIGHTNESSMARK=map(TempADC,100,2100,BRIGHTNESSMIN,BRIGHTNESSMAX); // dbgprint("potm:%d",BRIGHTNESSMARK); #ifdef Ledstrip FastLED.setBrightness(BRIGHTNESSMARK); #endif #ifdef HUB75 dma_display->setBrightness8(map(TempADC,100,2100,BRIGHTNESSMIN,BRIGHTNESSMAX)); #endif } if(ButtonOffTimer>ButtonTimeout){ ButtonStoptime=millis(); // time that no switch was presset will reset the counter. ButtonSequenceCounter=0; // reset the sequencecounter if (ShortPressFlag==1){ // Serial.printf("Short press detected\n"); buttonPushCounter = (buttonPushCounter + 1) % 13; #ifdef HUB75 dma_display->clearScreen(); #endif Serial.printf("Pattern Mode changed to: %d\n",buttonPushCounter); ShortPressFlag=0; } } if ((ButtonOnTimer>LongerPress)&&(ButtonOnTimer<(LongerPress+ShortPress))){LongerPressFlag=1;} else if ((ButtonOnTimer>LongPress)&&(ButtonOnTimer<(LongPress+ShortPress))){LongPressFlag=1;} else if ((ButtonOnTimer>ShortPress)&&(ButtonOnTimer<(2*ShortPress))){ ShortPressFlag=1; if((millis()-PreviousPressTime) NoiseTresshold){ int freq = BucketFrequency(i); int iBand = 0; while (iBand < numBands){ if (freq < BandCutoffTable[iBand])break; iBand++; } if (iBand > numBands)iBand = numBands; FreqBins[iBand]+= vReal[i]; // float scaledValue = vReal[i]; // if (scaledValue > peak[iBand]) // peak[iBand] = scaledValue; } } // bufmd[0]=FreqBins[12]; #if PrintRAWBins for ( int y=0; yDemoTreshold)LastDoNothingTime = millis(); // if there is signal in any off the bands[>2] then no demo mode // Serial.printf("gVu: %d\n",(int) gVU); for(int j=0;j allBandsPeak){ allBandsPeak = FreqBins[i]; } } if (allBandsPeak < 1)allBandsPeak = 1; // The followinf picks allBandsPeak if it's gone up. If it's gone down, it "averages" it by faking a running average of GAIN_DAMPEN past peaks allBandsPeak = max(allBandsPeak, ((lastAllBandsPeak * (GAIN_DAMPEN-1)) + allBandsPeak) / GAIN_DAMPEN); // Dampen rate of change a little bit on way down lastAllBandsPeak = allBandsPeak; if (allBandsPeak < 80000)allBandsPeak = 80000; for (int i = 0; i < numBands; i++){ FreqBins[i] /= (allBandsPeak * 1.0f); } // Process the FFT data into bar heights for (int band = 0; band < numBands; band++) { int barHeight = FreqBins[band]*kMatrixHeight-1; //(AMPLITUDE); if (barHeight > TOP-2) barHeight = TOP-2; // Small amount of averaging between frames barHeight = ((oldBarHeights[band] * 1) + barHeight) / 2; // Move peak up if (barHeight > peak[band]) { peak[band] = min(TOP, barHeight); PeakFlag[band]=1; } bndcounter[band]+=barHeight; // ten behoeve calibratie // if there hasn't been much of a input signal for a longer time ( see settings ) go to demo mode if ((millis() - LastDoNothingTime) > DemoAfterSec && DemoFlag==false) { dbgprint("In loop 1: %d", millis() - LastDoNothingTime); DemoFlag=true; // first store current mode so we can go back to it after wake up DemoModeMem=buttonPushCounter; AutoModeMem=autoChangePatterns; autoChangePatterns=false; buttonPushCounter=12; #ifdef HUB75 dma_display->clearScreen(); #endif dbgprint("Automode is turned of because of demo"); } // Wait,signal is back? then wakeup! else if (DemoFlag==true && (millis() - LastDoNothingTime) < DemoAfterSec ) { //("In loop 2: %d", millis() - LastDoNothingTime); // while in demo the democounter was reset due to signal on one of the bars. // So we need to exit demo mode. #ifdef HUB75 dma_display->clearScreen(); #endif buttonPushCounter=DemoModeMem; // restore settings dbgprint ("automode setting restored to: %d",AutoModeMem); autoChangePatterns=AutoModeMem;// restore settings DemoFlag=false; } #if BottomRowAlwaysOn if (barHeight==0)barHeight=1; // make sure there is always one bar that lights up #endif // Now visualize those bar heights switch (buttonPushCounter) { case 0: #ifdef HUB75 PeakDirection=down; BoxedBars(band, barHeight); BluePeak(band); #endif #ifdef Ledstrip changingBarsLS(band, barHeight); #endif break; case 1: #ifdef HUB75 PeakDirection=down; BoxedBars2(band, barHeight); BluePeak(band); #endif #ifdef Ledstrip TriBarLS(band, barHeight); TriPeakLS(band); #endif break; case 2: #ifdef HUB75 PeakDirection=down; BoxedBars3(band, barHeight); RedPeak(band); #endif #ifdef Ledstrip rainbowBarsLS(band, barHeight); NormalPeakLS(band, PeakColor1); #endif break; case 3: #ifdef HUB75 PeakDirection=down; RedBars(band, barHeight); BluePeak(band); #endif #ifdef Ledstrip purpleBarsLS(band, barHeight); NormalPeakLS(band, PeakColor2); #endif break; case 4: #ifdef HUB75 PeakDirection=down; ColorBars(band, barHeight); #endif #ifdef Ledstrip SameBarLS(band, barHeight); NormalPeakLS(band, PeakColor3); #endif break; case 5: #ifdef HUB75 PeakDirection=down; Twins(band, barHeight); WhitePeak(band); #endif #ifdef Ledstrip SameBar2LS(band, barHeight); NormalPeakLS(band, PeakColor3); #endif break; case 6: #ifdef HUB75 PeakDirection=down; Twins2(band, barHeight); WhitePeak(band); #endif #ifdef Ledstrip centerBarsLS(band, barHeight); #endif break; case 7: #ifdef HUB75 PeakDirection=down; TriBars(band, barHeight); TriPeak(band); #endif #ifdef Ledstrip centerBars2LS(band, barHeight); #endif break; case 8: #ifdef HUB75 PeakDirection=up; TriBars(band, barHeight); TriPeak(band); #endif #ifdef Ledstrip centerBars3LS(band, barHeight); #endif break; case 9: #ifdef HUB75 PeakDirection=down; centerBars(band, barHeight); #endif #ifdef Ledstrip BlackBarLS(band, barHeight); outrunPeakLS(band); #endif break; case 10: #ifdef HUB75 PeakDirection=down; centerBars2(band, barHeight); #endif #ifdef Ledstrip BlackBarLS(band, barHeight); NormalPeakLS(band, PeakColor5); #endif break; case 11: #ifdef HUB75 PeakDirection=down; BlackBars(band, barHeight); DoublePeak(band); #endif #ifdef Ledstrip BlackBarLS(band, barHeight); TriPeak2LS(band); #endif break; case 12: #ifdef HUB75 make_fire(); // go to demo mode #endif #ifdef Ledstrip matrix->fillRect(0, 0, matrix->width(), 1, 0x0000); // delete the VU meter make_fire(); #endif break; } // Save oldBarHeights for averaging later oldBarHeights[band] = barHeight; } // for calibration //bndcounter[h]+=barHeight; if (loopcounter==256){ loopcounter=0; #if CalibratieLog Calibration(); for(int g=0;g Peakdelay){PeakTimer[band]=0;PeakFlag[band]=0;} } else if ((peak[band] > 0) &&(PeakDirection==up)){ peak[band] += 1; if (peak[band]>(kMatrixHeight+10))peak[band]=0; } // when to far off screen then reset peak height else if ((peak[band] > 0)&&(PeakDirection==down)){ peak[band] -= 1;} } colorTimer++; } EVERY_N_MILLISECONDS(10)colorTimer++; // Used in some of the patterns EVERY_N_SECONDS(SecToChangePattern) { // if (FastLED.getBrightness() == 0) FastLED.setBrightness(BRIGHTNESSMARK); //Re-enable if lights are "off" if (autoChangePatterns){ buttonPushCounter = (buttonPushCounter + 1) % 12; #ifdef HUB75 dma_display->clearScreen(); #endif } } #ifdef Ledstrip delay(1); // needed to give fastled a minimum recovery time FastLED.show(); #endif } // loop end // BucketFrequency // // Return the frequency corresponding to the Nth sample bucket. Skips the first two // buckets which are overall amplitude and something else. int BucketFrequency(int iBucket){ if (iBucket <= 1)return 0; int iOffset = iBucket - 2; return iOffset * (samplingFrequency / 2) / (SAMPLEBLOCK / 2); } void DrawVUPixels(int i, int yVU, int fadeBy = 0){ CRGB VUC; if (i>(PANE_WIDTH/3)){ VUC.r=255; VUC.g=0; VUC.b=0 ; } else if (i>(PANE_WIDTH/5)){ VUC.r=255; VUC.g=255; VUC.b=0; } else{ // green VUC.r=0; VUC.g=255; VUC.b=0; } #ifdef Ledstrip int xHalf = matrix->width()/2; // matrix->drawPixel(xHalf-i-1, yVU, CRGB(0,100,0).fadeToBlackBy(fadeBy)); // matrix->drawPixel(xHalf+i, yVU, CRGB(0,100,0).fadeToBlackBy(fadeBy)); matrix->drawPixel(xHalf-i-1, yVU, CRGB(VUC.r,VUC.g,VUC.b).fadeToBlackBy(fadeBy)); matrix->drawPixel(xHalf+i, yVU, CRGB(VUC.r,VUC.g,VUC.b).fadeToBlackBy(fadeBy)); #endif #ifdef HUB75 int xHalf = PANE_WIDTH/2; dma_display->drawPixelRGB888(xHalf-i-2,yVU,VUC.r,VUC.g,VUC.b); //left side of screen line 0 dma_display->drawPixelRGB888(xHalf-i-2,yVU+1,VUC.r,VUC.g,VUC.b); //left side of screen line 1 dma_display->drawPixelRGB888(xHalf+i+1,yVU,VUC.r,VUC.g,VUC.b); // right side of screen line 0 dma_display->drawPixelRGB888(xHalf+i+1,yVU+1,VUC.r,VUC.g,VUC.b);// right side of screen line 1 #endif } void DrawVUMeter(int yVU){ static int iPeakVUy = 0; // size (in LED pixels) of the VU peak static unsigned long msPeakVU = 0; // timestamp in ms when that peak happened so we know how old it is const int MAX_FADE = 256; #ifdef HUB75 for(int x=0; xdrawPixelRGB888(x,yVU,0,0,0); dma_display->drawPixelRGB888(x,yVU+1,0,0,0); } #endif #ifdef Ledstrip matrix->fillRect(0, yVU, matrix->width(), 1, 0x0000); #endif if (iPeakVUy > 1){ int fade = MAX_FADE * (millis() - msPeakVU) / (float) 1000; DrawVUPixels(iPeakVUy, yVU, fade); } int xHalf = (PANE_WIDTH/2)-1; int bars = map(gVU, 0, MAX_VU, 1, xHalf); bars = min(bars, xHalf); if(bars > iPeakVUy){ msPeakVU = millis(); iPeakVUy = bars; } else if (millis() - msPeakVU > 1000)iPeakVUy = 0; for (int i = 0; i < bars; i++)DrawVUPixels(i, yVU); } void Calibration(void){ Serial.printf("BandCalibration_XXXX[%1d]=\n{",numBands); long Totalbnd=0; for (int g=0;gTotalbnd)Totalbnd=bndcounter[g]; } for (int g=0;g millis()) return; t = millis() + (1000 / FPS); // First, move all existing heat points up the display and fade for (i = rows - 1; i > 0; --i) { for (j = 0; j < cols; ++j) { uint8_t n = 0; if (pix[i - 1][j] > 0) n = pix[i - 1][j] - 1; pix[i][j] = n; } } // Heat the bottom row for (j = 0; j < cols; ++j) { i = pix[0][j]; if (i > 0) { pix[0][j] = random(NCOLORS - 6, NCOLORS - 2); } } // flare for (i = 0; i < nflare; ++i) { int x = flare[i] & 0xff; int y = (flare[i] >> 8) & 0xff; int z = (flare[i] >> 16) & 0xff; glow(x, y, z); if (z > 1) { flare[i] = (flare[i] & 0xffff) | ((z - 1) << 16); } else { // This flare is out for (int j = i + 1; j < nflare; ++j) { flare[j - 1] = flare[j]; } --nflare; } } newflare(); // Set and draw for (i = 0; i < rows; ++i) { for (j = 0; j < cols; ++j) { // matrix -> drawPixel(j, rows - i, colors[pix[i][j]]); CRGB COlsplit=colors[pix[i][j]]; #ifdef HUB75 dma_display->drawPixelRGB888(j,rows - i,COlsplit.r,COlsplit.g,COlsplit.b); #endif #ifdef Ledstrip matrix -> drawPixel(j, rows - i, colors[pix[i][j]]); #endif } } } void drawLogo(void){ #ifdef HUB75 // logo is 46 width and 54 high int i=0; int xyStart[2]={(kMatrixWidth/2)-23,2}; CRGB pix; for (int y=0;y<50;y++){ for (int x=0;x<46;x++){ pix=logo[i]; i++; dma_display->drawPixelRGB888(x+xyStart[0],y+xyStart[1],pix.r,pix.g,pix.b); } } dma_display->fillRect(5, 53, kMatrixWidth-10, 11, dma_display->color444(0, 0, 0)); delay(1000); dma_display->setTextSize(1); dma_display->setTextWrap(false); dma_display->setCursor(10,55); dma_display->print("Spectrum FFT "); dma_display->print(VERSION); delay(2000); dma_display->fillRect(5, 53, kMatrixWidth-10, 11, dma_display->color444(0, 0, 0)); dma_display->setTextColor(dma_display->color444(15,15,0)); dma_display->setCursor(10,55); dma_display->print(" Mark Donners "); delay(2000); #endif } void DisplayPrint(char * text){ #ifdef HUB75 dma_display->fillRect(8, 8, kMatrixWidth-16, 11, dma_display->color444(0,0 , 0)); dma_display->setTextSize(1); dma_display->setTextWrap(false); dma_display->setCursor(10,10); dma_display->print(text); delay(1000); dma_display->fillRect(8, 8, kMatrixWidth-16, 11, dma_display->color444(0,0 , 0)); #endif }