Created
November 8, 2025 07:53
-
-
Save Yuikawa-Akira/e49b30877a42e4d4aa6a51b14287d910 to your computer and use it in GitHub Desktop.
Ferrofluid_Audio_Visualizer
This file contains hidden or 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
| #include <FastLED.h> | |
| #include <M5Unified.h> | |
| #include <Avatar.h> | |
| #include <arduinoFFT.h> | |
| //Stack-chan | |
| using namespace m5avatar; | |
| Avatar avatar; | |
| //debug display | |
| M5Canvas canvas; | |
| //PIN AtomS3 | |
| #define PWM 5 | |
| #define MIC 6 | |
| #define VOL 7 | |
| #define AUDIO 8 | |
| #define LED_PIN 39 | |
| //FastLED | |
| #define NUM_LEDS 29 | |
| CRGB leds[NUM_LEDS]; | |
| //PWM | |
| #define PWM_CH 1 | |
| #define PWM_FREQ 5000 | |
| #define BIT_NUM 12 //12bit 4096 | |
| //FFT | |
| #define SAMPL_FREQ 20000 | |
| const uint16_t FFTsamples = 512; //512 only | |
| double vReal[FFTsamples]; | |
| double vImag[FFTsamples]; | |
| ArduinoFFT<double> FFT = ArduinoFFT<double>(vReal, vImag, FFTsamples, SAMPL_FREQ); | |
| unsigned int sampling_period_us; | |
| //sampling | |
| void sample(int nsamples, int pin) { | |
| for (int i = 0; i < nsamples; i++) { | |
| unsigned long t = micros(); | |
| vReal[i] = (double)analogRead(pin) / 4095.0 * 3.6 + 0.1132; // ESP32 ADC?? | |
| vImag[i] = 0; | |
| while ((micros() - t) < sampling_period_us) | |
| ; | |
| } | |
| } | |
| //DC cut | |
| void DCRemoval(double *vData, uint16_t samples) { | |
| double mean = 0; | |
| for (uint16_t i = 1; i < samples; i++) { | |
| mean += vData[i]; | |
| } | |
| mean /= samples; | |
| for (uint16_t i = 1; i < samples; i++) { | |
| vData[i] -= mean; | |
| } | |
| } | |
| //Find the maximum value from start to end | |
| double find_arrayMax(double *a, int start, int end) { | |
| int max = 0; | |
| for (int i = start; i <= end; i++) { | |
| if (max < a[i]) { | |
| max = a[i]; | |
| } | |
| } | |
| return max; | |
| } | |
| //MODE 0:Volume 1:Mic 2:Audio Nomal 3:Audio Low cut | |
| int Mode = 0; | |
| int volume = 0; | |
| int magValue0 = 0; | |
| int magValue1 = 0; | |
| int magValue2 = 0; | |
| //EQ | |
| int EQ_filter0[8]; //buffer | |
| int EQ_filter1[8] = { 50, 50, 50, 50, 50, 60, 80, 0 }; //Mic MAX9814 40dB | |
| int EQ_filter2[8] = { 50, 50, 50, 50, 55, 60, 60, 70 }; //Audio Nomal | |
| int EQ_filter3[8] = { 5, 5, 10, 80, 80, 80, 80, 80 }; //Audio Low cut | |
| double band_peak[8]; | |
| int band[8]; //debug display | |
| void setup() { | |
| //M5 cfg | |
| auto cfg = M5.config(); | |
| M5.begin(cfg); | |
| //pin | |
| pinMode(MIC, INPUT); | |
| pinMode(AUDIO, INPUT); | |
| pinMode(VOL, INPUT); | |
| pinMode(PWM, OUTPUT); | |
| //pwm | |
| ledcSetup(PWM_CH, PWM_FREQ, BIT_NUM); | |
| ledcAttachPin(PWM, PWM_CH); | |
| //FFT | |
| sampling_period_us = round(1000000 * (1.0 / SAMPL_FREQ)); | |
| //LED | |
| FastLED.addLeds<SK6812, LED_PIN, GRB>(leds, NUM_LEDS); | |
| FastLED.setBrightness(20); | |
| for (int i = 0; i < NUM_LEDS; i++) { | |
| leds[i] = CRGB::White; | |
| } | |
| FastLED.show(); | |
| //debug | |
| canvas.createSprite(128, 128); | |
| //Serial.begin(115200); | |
| //avatar | |
| avatar.setRotation(0); | |
| avatar.setScale(0.6); | |
| avatar.setPosition(-58, -100); | |
| avatar.init(); // start drawing | |
| } | |
| void loop() { | |
| M5.update(); | |
| volume = analogRead(VOL); | |
| //Mode | |
| if (M5.BtnA.wasPressed()) { | |
| Mode = (Mode + 1) % 4; | |
| if (Mode == 0) { | |
| canvas.fillScreen(TFT_BLACK); | |
| canvas.pushSprite(&M5.Display, 0, 0); | |
| avatar.start(); | |
| } | |
| if (Mode >= 1) { | |
| avatar.stop(); | |
| canvas.fillScreen(TFT_BLACK); | |
| canvas.pushSprite(&M5.Display, 0, 0); | |
| } | |
| } | |
| //Volume | |
| if (Mode == 0) { | |
| magValue0 = volume; | |
| magValue0 = constrain(magValue0, 0, 4000); //Limiting output power | |
| ledcWrite(PWM_CH, magValue0); | |
| float ratio = map(volume, 0, 4095, 10, 120) * 0.01; | |
| avatar.setMouthOpenRatio(ratio); | |
| //debug | |
| //Serial.println(magValue0); | |
| } | |
| //Mic | |
| if (Mode == 1) { | |
| sample(FFTsamples, MIC); | |
| memcpy(EQ_filter0, EQ_filter1, sizeof(EQ_filter1)); | |
| } | |
| //Audio | |
| if (Mode >= 2) { | |
| sample(FFTsamples, AUDIO); | |
| if (Mode == 2) { | |
| memcpy(EQ_filter0, EQ_filter2, sizeof(EQ_filter2)); //Nomal | |
| } | |
| if (Mode == 3) { | |
| memcpy(EQ_filter0, EQ_filter3, sizeof(EQ_filter3)); //Low cut | |
| } | |
| } | |
| //FFT | |
| DCRemoval(vReal, FFTsamples); | |
| FFT.windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD); | |
| FFT.compute(FFT_FORWARD); | |
| FFT.complexToMagnitude(); | |
| //Find the maximum value for each band | |
| if (Mode >= 1) { | |
| band_peak[0] = find_arrayMax(vReal, 1, 4) * EQ_filter0[0]; | |
| band_peak[1] = find_arrayMax(vReal, 5, 10) * EQ_filter0[1]; | |
| band_peak[2] = find_arrayMax(vReal, 11, 17) * EQ_filter0[2]; | |
| band_peak[3] = find_arrayMax(vReal, 18, 31) * EQ_filter0[3]; | |
| band_peak[4] = find_arrayMax(vReal, 32, 59) * EQ_filter0[4]; | |
| band_peak[5] = find_arrayMax(vReal, 60, 110) * EQ_filter0[5]; | |
| band_peak[6] = find_arrayMax(vReal, 111, 213) * EQ_filter0[6]; | |
| band_peak[7] = find_arrayMax(vReal, 214, 255) * EQ_filter0[7]; | |
| } | |
| //OUTPUT | |
| if (Mode == 1) { | |
| magValue1 = (int)find_arrayMax(band_peak, 0, 7); | |
| magValue1 = map(magValue1, 0, 400, 0, -1000 + volume); | |
| magValue1 = constrain(magValue1, 0, 4000); | |
| ledcWrite(PWM_CH, magValue1); | |
| } | |
| if (Mode >= 2) { | |
| magValue2 = (int)find_arrayMax(band_peak, 0, 7); | |
| magValue2 = map(magValue2, 0, 400, 500, 1000 + volume); | |
| magValue2 = constrain(magValue2, 0, 4000); | |
| ledcWrite(PWM_CH, magValue2); | |
| } | |
| //debug | |
| if (Mode >= 1) { | |
| canvas.fillScreen(TFT_BLACK); | |
| for (int i = 0; i < 8; i++) { | |
| band[i] = map(band_peak[i], 0, 400, 2, 124); | |
| } | |
| canvas.fillRect(2, 126, 14, -band[0], TFT_WHITE); | |
| canvas.fillRect(18, 126, 14, -band[1], TFT_WHITE); | |
| canvas.fillRect(34, 126, 14, -band[2], TFT_WHITE); | |
| canvas.fillRect(50, 126, 14, -band[3], TFT_WHITE); | |
| canvas.fillRect(66, 126, 14, -band[4], TFT_WHITE); | |
| canvas.fillRect(82, 126, 14, -band[5], TFT_WHITE); | |
| canvas.fillRect(98, 126, 14, -band[6], TFT_WHITE); | |
| canvas.fillRect(114, 126, 14, -band[7], TFT_WHITE); | |
| canvas.pushSprite(&M5.Display, 0, 0); | |
| } | |
| /* | |
| //debug | |
| Serial.print(band_peak[0]); | |
| Serial.print(", "); | |
| Serial.print(band_peak[1]); | |
| Serial.print(", "); | |
| Serial.print(band_peak[2]); | |
| Serial.print(", "); | |
| Serial.print(band_peak[3]); | |
| Serial.print(", "); | |
| Serial.print(band_peak[4]); | |
| Serial.print(", "); | |
| Serial.print(band_peak[5]); | |
| Serial.print(", "); | |
| Serial.print(band_peak[6]); | |
| Serial.print(", "); | |
| Serial.println(band_peak[7]); | |
| */ | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment