Skip to content

Instantly share code, notes, and snippets.

@Yuikawa-Akira
Created November 8, 2025 07:53
Show Gist options
  • Select an option

  • Save Yuikawa-Akira/e49b30877a42e4d4aa6a51b14287d910 to your computer and use it in GitHub Desktop.

Select an option

Save Yuikawa-Akira/e49b30877a42e4d4aa6a51b14287d910 to your computer and use it in GitHub Desktop.
Ferrofluid_Audio_Visualizer
#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