驴友花雕 发表于 2025-7-10 07:32:17

【Arduino 动手做】矩阵 16X16 图形音频频谱分析仪

具有大量设置和功能的 Graph Spectrum Analyzer

光谱输出开启:
显示器 1602
4 个块的矩阵 8x8 (MAX7219)
WS2812 可寻址矩阵
亮度调整
色域调整(适用于 WS2812)
调整增益和杂色减少
调整动画平滑度
音量设置:
固定
来自电位计
自动
最高积分
开 关
挂起时间
坠落速度
按频率手动采样

算法
频谱分析,在输出端,我们有一个光谱波段值数组(128 个波段)
按每个波段的较低值(128 个波段)进行筛选
从 128 个通道过渡到 16 个通道,同时保持基于线性的带间值
求最大值以校正矩阵上条形的高度
将带材的净“重量”转换为模具的高度
将条带发送到矩阵
计算最高积分的位置并将其发送到 mtaritsa
顺便过滤较高的峰,从体积中校正柱的高度,等等

















驴友花雕 发表于 2025-7-10 07:34:27

【Arduino 动手做】矩阵 16X16 图形音频频谱分析仪

项目代码

// -------------------------------- НАСТРОЙКИ --------------------------------
// матрица
#define WIDTH 16          // ширина матрицы (число диодов)
#define HEIGHT 16         // высота матрицы (число диодов)
#define BRIGHTNESS 240   // яркость (0 - 255)

// цвета высоты полос спектра. Длины полос задаются примерно в строке 95
#define COLOR1 CRGB::Green
#define COLOR2 CRGB::Yellow
#define COLOR3 CRGB::Orange
#define COLOR4 CRGB::Red

// сигнал
#define INPUT_GAIN 1.5    // коэффициент усиления входного сигнала
#define LOW_PASS 30       // нижний порог чувствительности шумов (нет скачков при отсутствии звука)
#define MAX_COEF 1.1      // коэффициент, который делает "максимальные" пики чуть меньше максимума, для более приятного восприятия
#define NORMALIZE 0       // нормализовать пики (столбики низких и высоких частот будут одинаковой длины при одинаковой громкости) (1 вкл, 0 выкл)

// анимация
#define SMOOTH 0.3      // плавность движения столбиков (0 - 1)
#define DELAY 4         // задержка между обновлениями матрицы (периодичность основного цикла), миллиисекунды

// громкость
#define DEF_GAIN 50       // максимальный порог по умолчанию (при MANUAL_GAIN или AUTO_GAIN игнорируется)
#define MANUAL_GAIN 0   // ручная настройка потенциометром на громкость (1 вкл, 0 выкл)
#define AUTO_GAIN 1       // автонастройка по громкости (экспериментальная функция) (1 вкл, 0 выкл)

// точки максимума
#define MAX_DOTS 1      // включить/выключить отрисовку точек максимума (1 вкл, 0 выкл)
#define MAX_COLOR CRGB::Red // цвет точек максимума
#define FALL_DELAY 50   // скорость падения точек максимума (задержка, миллисекунды)
#define FALL_PAUSE 700    // пауза перед падением точек максимума, миллисекунды

// массив тонов, расположены примерно по параболе. От 80 Гц до 16 кГц
byte posOffset = {2, 3, 4, 6, 8, 10, 12, 14, 16, 20, 25, 30, 35, 60, 80, 100, 120};
// -------------------------------- НАСТРОЙКИ --------------------------------

// ---------------------- ПИНЫ ----------------------
// для увеличения точности уменьшаем опорное напряжение,
// выставив EXTERNAL и подключив Aref к выходу 3.3V на плате через делитель
// GND --- --- REF --- --- 3V3

#define AUDIO_IN 0         // пин, куда подключен звук
#define DIN_PIN 6         // пин Din ленты (через резистор!)
#define POT_PIN 7         // пин потенциометра настройки (если нужен MANUAL_GAIN)
// ---------------------- ПИНЫ ----------------------

// --------------- ДЛЯ РАЗРАБОТЧИКОВ ---------------
#define NUM_LEDS WIDTH * HEIGHT
#define FHT_N 256         // ширина спектра х2
#define LOG_OUT 1
#include <FHT.h>         // преобразование Хартли
#include <FastLED.h>
CRGB leds;

#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

int gain = DEF_GAIN;   // усиление по умолчанию
unsigned long gainTimer, fallTimer;
byte maxValue;
float k = 0.05, maxValue_f = 0.0;
int maxLevel;
byte posLevel_old;
unsigned long timeLevel, mainDelay;
boolean fallFlag;
// --------------- ДЛЯ РАЗРАБОТЧИКОВ ---------------

void setup() {
// поднимаем частоту опроса аналогового порта до 38.4 кГц, по теореме
// Котельникова (Найквиста) максимальная частота дискретизации будет 19 кГц
// http://yaab-arduino.blogspot.ru/2015/02/fast-sampling-from-analog-input.html
sbi(ADCSRA, ADPS2);
cbi(ADCSRA, ADPS1);
sbi(ADCSRA, ADPS0);

analogReference(EXTERNAL);

Serial.begin(9600);
FastLED.setBrightness(BRIGHTNESS);
FastLED.addLeds<WS2812B, DIN_PIN, GRB>(leds, NUM_LEDS);
}

void loop() {
if (millis() - mainDelay > DELAY) {   // итерация главного цикла
    mainDelay = millis();

    analyzeAudio();   // функция FHT, забивает массив fht_log_out[] величинами по спектру

    for (int i = 0; i < 128; i ++) {
      // вот здесь сразу фильтруем весь спектр по минимальному LOW_PASS
      if (fht_log_out < LOW_PASS) fht_log_out = 0;

      // усиляем сигнал
      fht_log_out = (float)fht_log_out * INPUT_GAIN;

      // уменьшаем громкость высоких частот (пропорционально частоте) если включено
      if (NORMALIZE) fht_log_out = (float)fht_log_out / ((float)1 + (float)i / 128);
    }

    maxValue = 0;
    FastLED.clear();// очистить матрицу
    for (byte pos = 0; pos < WIDTH; pos++) {    // для кажого столбца матрицы
      int posLevel = fht_log_out];
      byte linesBetween;
      if (pos > 0 && pos < WIDTH) {
      linesBetween = posOffset - posOffset;
      for (byte i = 0; i < linesBetween; i++) {// от предыдущей полосы до текущей
          posLevel += (float) ((float)i / linesBetween) * fht_log_out - linesBetween + i];
      }
      linesBetween = posOffset - posOffset;
      for (byte i = 0; i < linesBetween; i++) {// от предыдущей полосы до текущей
          posLevel += (float) ((float)i / linesBetween) * fht_log_out + linesBetween - i];
      }
      }

      // найти максимум из пачки тонов
      if (posLevel > maxValue) maxValue = posLevel;

      // фильтрация длины столбиков, для их плавного движения
      posLevel = posLevel * SMOOTH + posLevel_old * (1 - SMOOTH);
      posLevel_old = posLevel;

      // преобразовать значение величины спектра в диапазон 0..HEIGHT с учётом настроек
      posLevel = map(posLevel, LOW_PASS, gain, 0, HEIGHT);
      posLevel = constrain(posLevel, 0, HEIGHT);

      if (posLevel > 0) {
      for (int j = 0; j < posLevel; j++) {               // столбцы
          uint32_t color;
          if (j < 5) color = COLOR1;
          else if (j < 10) color = COLOR2;
          else if (j < 13) color = COLOR3;
          else if (j < 15) color = COLOR4;

          if (pos % 2 != 0)                                 // если чётная строка
            leds = color;                  // заливаем в прямом порядке
          else                                              // если нечётная
            leds = color;      // заливаем в обратном порядке
      }
      }

      if (posLevel > 0 && posLevel > maxLevel) {    // если для этой полосы есть максимум, который больше предыдущего
      maxLevel = posLevel;                        // запомнить его
      timeLevel = millis();                     // запомнить время
      }

      // если точка максимума выше нуля (или равна ему) - включить пиксель
      if (maxLevel >= 0 && MAX_DOTS) {
      if (pos % 2 != 0)                                 // если чётная строка
          leds] = MAX_COLOR;                  // заливаем в прямом порядке
      else                                              // если нечётная
          leds - 1] = MAX_COLOR;      // заливаем в обратном порядке
      }

      if (fallFlag) {                                           // если падаем на шаг
      if ((long)millis() - timeLevel > FALL_PAUSE) {   // если максимум держался на своей высоте дольше FALL_PAUSE
          if (maxLevel >= 0) maxLevel--;            // уменьшить высоту точки на 1
          // внимание! Принимает минимальное значение -1 !
      }
      }
    }
    FastLED.show();// отправить на матрицу

    fallFlag = 0;                                 // сбросить флаг падения
    if (millis() - fallTimer > FALL_DELAY) {      // если настало время следующего падения
      fallFlag = 1;                               // поднять флаг
      fallTimer = millis();
    }

    // если разрешена ручная настройка уровня громкости
    if (MANUAL_GAIN) gain = map(analogRead(POT_PIN), 0, 1023, 0, 150);

    // если разрешена авто настройка уровня громкости
    if (AUTO_GAIN) {
      if (millis() - gainTimer > 10) {      // каждые 10 мс
      maxValue_f = maxValue * k + maxValue_f * (1 - k);
      // если максимальное значение больше порога, взять его как максимум для отображения
      if (maxValue_f > LOW_PASS) gain = (float) MAX_COEF * maxValue_f;
      // если нет, то взять порог побольше, чтобы шумы вообще не проходили
      else gain = 100;
      gainTimer = millis();
      }
    }
}
}

void analyzeAudio() {
for (int i = 0 ; i < FHT_N ; i++) {
    int sample = analogRead(AUDIO_IN);
    fht_input = sample; // put real data into bins
}
fht_window();// window the data for better frequency response
fht_reorder(); // reorder the data before doing the fht
fht_run();   // process the data in the fht
fht_mag_log(); // take the output of the fht
}

/*
   Алгоритм работы:
   Анализ спектра, на выходе имеем массив величин полос спектра (128 полос)
   Фильтрация по нижним значениям для каждой полосы (128 полос)
   Переход от 128 полос к 16 полосам с сохранением межполосных значений по линейной зависимости
   Поиск максимумов для коррекции высоты столбиков на матрице
   Перевод чистого "веса" полосы к высоте матрицы
   Отправка полос на матрицу
   Расчёт позиций точек максимума и отправка их на мтарицу

   Мимоходом фильтрация верхних пиков, коррекция высоты столбиков от громкости и прочее
*/

驴友花雕 发表于 2025-7-10 07:38:52

【Arduino 动手做】矩阵 16X16 图形音频频谱分析仪

【Arduino 动手做】矩阵 16X16 图形音频频谱分析仪
项目链接:https://alexgyver.ru/fhtspectrumanalyzer/
项目作者:alexgyver
项目参考:https://alexgyver.ru/arduino-first/#%E2%96%B6%D0%9F%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D1%8B_AlexGyver%E2%97%80
项目视频 :https://www.youtube.com/watch?v=3b66UxMQoiw
项目代码:
https://github.com/AlexGyver/FHTSpectrumAnalyzer/blob/master/Firmware/spertrumWS2812_16x16_full/spertrumWS2812_16x16_full.ino
https://github.com/AlexGyver/FHTSpectrumAnalyzer/archive/master.zip



页: [1]
查看完整版本: 【Arduino 动手做】矩阵 16X16 图形音频频谱分析仪