25浏览
查看: 25|回复: 2

[项目] 【Arduino 动手做】比FFT快4倍的FHT音频频谱可视化工具

[复制链接]
这个项目与我之前的项目类似,但使用了 FHT 库,其速度至少比 FFT 快 4 倍。

Arduino FHT 库
FHT 的作用与更为人熟知的 FFT 完全相同,但与 FFT 不同的是,它只使用实数数据,而 FFT 处理复数数据,因此 FHT 使用一半的处理能力和一半的系统内存。

该库使用单个数组进行数据采样,而不是FFT库使用的两个数组,一个用于实值,一个用于虚值,它还简化了复杂的数学计算,避免了使用复杂浮点对数计算的循环,而是使用了一些数值表。

缺点是它总是返回用于执行采样的数组大小的一半的值,这会导致音频范围两端的分辨率和精度有所损失。不过,在具体情况下,我们不需要开发测量仪器,而只需开发一个简单的小工具即可,我们不需要极高的精度,只需按照音乐节奏点亮几个LED即可。

当然,这里不是讨论 FHT 库的模态操作的正确地方,任何希望了解更多信息的人都可以参考开放音乐实验室网站,在那里他们可以找到所有必要的文档来了解所使用的技巧、各种功能的详细解释以及一些已经为 Arduino 编写的通用示例。

FFT 库速度非常快,以至于有必要在代码中插入延迟以减少闪烁。您会立即注意到最终结果,显示速度比使用 FFT 库的版本更快,响应速度也更快,因为使用 FFT 库的版本经常会显得落后于音乐。由于系统资源占用较少,我们可以添加一些代码来管理其他功能。

新版本
该项目与以前的版本相同,因此已经制作了以前版本的人只需要添加一个按钮和一个 10K 电阻并更新代码。

该版本除了实现FHT库之外,还增加了以下功能:

有一个额外的按钮用于调节亮度;
允许在 EEPROM 中保存颜色和亮度设置,以便在下次打开时重新加载它们;
按下按钮时和打开时显示颜色和亮度设置级别(可以禁用)。


【Arduino 动手做】比FFT快4倍的FHT音频频谱可视化工具图1

【Arduino 动手做】比FFT快4倍的FHT音频频谱可视化工具图3

【Arduino 动手做】比FFT快4倍的FHT音频频谱可视化工具图2

驴友花雕  中级技神
 楼主|

发表于 4 小时前

【Arduino 动手做】比FFT快4倍的FHT音频频谱可视化工具

项目代码

  1. /*
  2.   Copyright (c) 2020 Janux
  3.   Permission is hereby granted, free of charge, to any person obtaining a copy
  4.   of this software and associated documentation files (the "Software"), to deal
  5.   in the Software without restriction, including without limitation the rights
  6.   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7.   copies of the Software, and to permit persons to whom the Software is
  8.   furnished to do so, subject to the following conditions:
  9.   The above copyright notice and this permission notice shall be included in all
  10.   copies or substantial portions of the Software.
  11.   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  12.   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  13.   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  14.   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  15.   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  16.   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  17.   SOFTWARE.
  18.   Based on an original project for the MAX72xx LED matrix and FFT lib made from Shajeeb.
  19.   Configuration settings section based on work of Ragnar Ranøyen Homb from Norvegian Creation.
  20. */
  21. #define LIN_OUT 1                //FHT linear output magnitude
  22. #define FHT_N 128                //set SAMPLES for FHT, Must be a power of 2
  23. #include <FHT.h>
  24. #define xres 32                 //Total number of columns in the display, must be <= SAMPLES/2
  25. #define yres 8                  //Total number of rows in the display
  26. #define ledPIN 6                //out pint to control Leds
  27. #define NUM_LEDS (xres * yres)  //total leds in Matrix
  28. #include <Adafruit_NeoPixel.h>
  29. #define colorPIN 5        //pin to change ledcolor
  30. #define brightnessPIN 10  //pin to change brightness
  31. byte displaycolor = 0;    //default color value
  32. byte brightness = 1;      //default brightness level
  33. #include <EEPROM.h>
  34. #define CONFIG_START 32         //Memory start location
  35. #define CONFIG_VERSION "VER01"  //Config version configuration
  36. typedef struct {
  37.   char version[6];
  38.   byte displaycolor;
  39.   byte brightness;
  40. } configuration_type;
  41. configuration_type CONFIGURATION = {
  42.   CONFIG_VERSION,
  43.   displaycolor,
  44.   brightness
  45. };
  46. byte yvalue;
  47. int peaks[xres];
  48. byte state = HIGH;                    // the current reading from the input pin
  49. byte previousState = LOW;             // the previous reading from the input pin
  50. unsigned long lastDebounceTime = 0;   // the last time the output pin was toggled
  51. unsigned long debounceDelay = 100;    // the debounce time; increase if the output flickers
  52. byte data_avgs[xres]; //Array for samplig
  53. // Parameter 1 = number of leds in matrix
  54. // Parameter 2 = pin number
  55. // Parameter 3 = pixel type flags, add together as needed:
  56. //   NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
  57. //   NEO_KHZ400  400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
  58. //   NEO_GRB     Pixels are wired for GRB bitstream (most NeoPixel products)
  59. //   NEO_RGB     Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
  60. Adafruit_NeoPixel pixel = Adafruit_NeoPixel(NUM_LEDS, ledPIN, NEO_GRB + NEO_KHZ800);
  61. // EQ filter
  62. byte eq[32] = {
  63.   60, 65, 70, 75, 80, 85, 90, 95,
  64.   100, 100, 100, 100, 100, 100, 100, 100,
  65.   100, 100, 100, 100, 100, 100, 100, 100,
  66.   115, 125, 140, 160, 185, 200, 200, 200
  67. };
  68. bool EQ_ON = true; // set to false to disable eq
  69. //Define 5 set of colors for leds, 0 for single custom color
  70. byte colors[][8] = {
  71.   {170, 160, 150, 140, 130, 120, 1, 1},
  72.   {1, 5, 10, 15, 20, 25, 90, 90},
  73.   {90, 85, 80, 75, 70, 65, 1, 1},
  74.   {90, 90, 90, 30, 30, 30, 1, 1},
  75.   {170, 160, 150, 140, 130, 120, 110, 0}
  76. };
  77. //Define chars for display settings
  78. byte charBitmap[] = {
  79.   0x1C, 0x10, 0x10, 0x10, 0x10, 0x1C, 0x08, 0x18, 0x08, 0x08, 0x08, 0x1C,
  80.   0x0C, 0x12, 0x04, 0x08, 0x10, 0x1E, 0x0C, 0x12, 0x02, 0x06, 0x12, 0x0C,
  81.   0x10, 0x10, 0x10, 0x14, 0x1E, 0x04, 0x1E, 0x10, 0x1E, 0x02, 0x12, 0x0C,
  82.   0x1E, 0x10, 0x10, 0x1E, 0x12, 0x1E, 0x1E, 0x02, 0x04, 0x08, 0x08, 0x08,
  83.   0x0C, 0x12, 0x0C, 0x12, 0x12, 0x0C, 0x1C, 0x12, 0x1C, 0x12, 0x12, 0x1C
  84. };
  85. void setup() {
  86.   pixel.begin();           //initialize Led Matrix
  87.   //Begin FFT operations
  88.   ADCSRA = 0b11100101;    // set ADC to free running mode and set pre-scaler to 32 (0xe5)
  89.   ADMUX =  0b00000000;    // use pin A0 and external voltage reference
  90.   // Read config data from EEPROM
  91.   if (loadConfig()) {
  92.     displaycolor = CONFIGURATION.displaycolor;
  93.     brightness = CONFIGURATION.brightness;
  94.   }
  95.   //Set brightness loaded from EEPROM
  96.   pixel.setBrightness(brightness * 24 + 8);
  97.   //Show current config on start
  98.   //change true to false if you don't want this
  99.   showSettings(3, true);
  100. }
  101. void loop() {
  102.   while (1) {            // reduces jitter
  103.     Sampling();          // FHT Library use only one data array
  104.     RearrangeFHT();      // re-arrange FHT result to match with no. of display columns
  105.     SendToDisplay();     // send to display according measured value
  106.     colorChange();       // check if button pressed to change color
  107.     brightnessChange();  // check if button pressed to change brightness
  108.     delay(10);           // delay to reduce flickering (FHT is too fast :D)
  109.   }
  110. }
  111. void Sampling() {
  112.   for (int i = 0; i < FHT_N; i++) {
  113.     while (!(ADCSRA & 0x10));   // wait for ADC to complete current conversion ie ADIF bit set
  114.     ADCSRA = 0b11110101 ;       // clear ADIF bit so that ADC can do next operation (0xf5)
  115.     //ADLAR bit is 0, so the 10 bits of ADC Data registers are right aligned
  116.     byte m = ADCL;              // fetch adc data
  117.     byte j = ADCH;
  118.     int value = (j << 8) | m;   // form into an int
  119.     value -= 0x0200;            // form into a signed int
  120.     value <<= 6;                // form into a 16b signed int
  121.     fht_input[i] = value / 8;   // copy to fht input array after compressing
  122.   }
  123.   // ++ begin FHT data process -+-+--+-+--+-+--+-+--+-+--+-+--+-+-
  124.   fht_window();    // window the data for better frequency response
  125.   fht_reorder();   // reorder the data before doing the fht
  126.   fht_run();       // process the data in the fht
  127.   fht_mag_lin();   // take the output of the fht
  128. }
  129. void RearrangeFHT() {
  130.   // FHT return real value unsing only one array
  131.   // after fht_mag_lin() calling the samples value are in
  132.   // the first FHT_N/2 position of the array fht_lin_out[]
  133.   int step = (FHT_N / 2) / xres;
  134.   int c = 0;
  135.   for (int i = 0; i < (FHT_N / 2); i += step) {
  136.     data_avgs[c] = 0;
  137.     for (int k = 0 ; k < step ; k++) {
  138.       data_avgs[c] = data_avgs[c] + fht_lin_out[i + k];  // linear output magnitude
  139.     }
  140.     data_avgs[c] = data_avgs[c] / step ; // save avgs value
  141.     c++;
  142.   }
  143. }
  144. void SendToDisplay() {
  145.   for (int i = 0; i < xres; i++) {
  146.     if (EQ_ON)
  147.       data_avgs[i] = data_avgs[i] * (float)(eq[i]) / 100; // apply eq filter
  148.     data_avgs[i] = constrain(data_avgs[i], 0, 80);        // set max & min values for buckets to 0-80
  149.     data_avgs[i] = map(data_avgs[i], 0, 80, 0, yres);     // remap averaged values to yres 0-8
  150.     yvalue = data_avgs[i];
  151.     peaks[i] = peaks[i] - 1;                              // decay by one light
  152.     if (yvalue > peaks[i]) peaks[i] = yvalue;             // save peak if > previuos peak
  153.     yvalue = peaks[i];                                    // pick peak to display
  154.     setColumn(i, yvalue);                                 // draw columns
  155.   }
  156.   pixel.show();                                           // show column
  157. }
  158. // Light up leds of x column according to y value
  159. void setColumn(byte x, byte y) {
  160.   int led, i;
  161.   for (i = 0; i < yres; i++) {
  162.     led = GetLedFromMatrix(x, i); //retrieve current led by x,y coordinates
  163.     if (peaks[x] > i) {
  164.       switch (displaycolor) {
  165.         case 4:
  166.           if (colors[displaycolor][i] == 0) {
  167.             // show custom color with zero value in array
  168.             pixel.setPixelColor(led, 255, 255, 255); //withe
  169.           }
  170.           else {
  171.             // standard color defined in colors array
  172.             pixel.setPixelColor(led, Wheel(colors[displaycolor][i]));
  173.           }
  174.           break;
  175.         case 5:
  176.           //change color by column
  177.           pixel.setPixelColor(led, Wheel(x * 16));
  178.           break;
  179.         case 6:
  180.           //change color by row
  181.           pixel.setPixelColor(led, Wheel(i * y * 3));
  182.           break;
  183.         case 7:
  184.           //change color by... country :D
  185.           //Italy flagh
  186.           //if (x < 11) pixel.setPixelColor(led, 0, 255, 0);
  187.           //if (x > 10 && x < 21) pixel.setPixelColor(led, 255, 255, 255);
  188.           //if (x > 20) pixel.setPixelColor(led, 255, 0, 0);
  189.           //stars and stripes
  190.           if (i < yres - 2) {
  191.             if (x & 0x01) {
  192.               pixel.setPixelColor(led, 0, 0, 255);
  193.             }
  194.             else {
  195.               pixel.setPixelColor(led, 255, 0, 0);
  196.             }
  197.           }
  198.           else {
  199.             pixel.setPixelColor(led, 255, 255, 255);
  200.           }
  201.           break;
  202.         default:
  203.           //display colors defined in color array
  204.           pixel.setPixelColor(led, Wheel(colors[displaycolor][i]));
  205.       }   //END SWITCH
  206.     }
  207.     else {
  208.       //Light off leds
  209.       pixel.setPixelColor(led, pixel.Color(0, 0, 0));
  210.     }
  211.   }
  212. }
  213. //================================================================
  214. // Calculate a led number by x,y coordinates
  215. // valid for WS2812B with serpentine layout placed in horizzontal
  216. // and zero led at bottom right (DIN connector on the right side)
  217. // input value: x= 0 to xres-1 , y= 0 to yres-1
  218. // return a led number from 0 to NUM_LED
  219. //================================================================
  220. int GetLedFromMatrix(byte x, byte y) {
  221.   int led;
  222.   x = xres - x - 1;
  223.   if (x & 0x01) {
  224.     //Odd columns increase backwards
  225.     led = ((x + 1) * yres - y - 1);
  226.   }
  227.   else {
  228.     //Even columns increase normally
  229.     led = ((x + 1) * yres - yres + y);
  230.   }
  231.   return constrain(led, 0, NUM_LEDS);
  232. }
  233. //================================================================
  234. void colorChange() {
  235.   int reading = digitalRead(colorPIN);
  236.   if (reading == HIGH && previousState == LOW && millis() - lastDebounceTime > debounceDelay) {
  237.     displaycolor++;
  238.     if (displaycolor > 7) displaycolor = 0;
  239.     showSettings(1, true); //set to false if you don't want this
  240.     saveConfig();
  241.     lastDebounceTime = millis();
  242.   }
  243.   previousState = reading;
  244. }
  245. void brightnessChange() {
  246.   int reading = digitalRead(brightnessPIN);
  247.   if (reading == HIGH && previousState == LOW && millis() - lastDebounceTime > debounceDelay) {
  248.     brightness++;
  249.     if (brightness > 7) brightness = 0;
  250.     pixel.setBrightness(brightness * 24 + 8);
  251.     showSettings(2, true); //set to false if you don't want this
  252.     saveConfig();
  253.     lastDebounceTime = millis();
  254.   }
  255.   previousState = reading;
  256. }
  257. // Utility from Adafruit Neopixel demo sketch
  258. // Input a value 0 to 255 to get a color value.
  259. // The colours are a transition R - G - B - back to R.
  260. unsigned long Wheel(byte WheelPos) {
  261.   WheelPos = 255 - WheelPos;
  262.   if (WheelPos < 85) {
  263.     return pixel.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  264.   }
  265.   if (WheelPos < 170) {
  266.     WheelPos -= 85;
  267.     return pixel.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  268.   }
  269.   WheelPos -= 170;
  270.   return pixel.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  271. }
  272. // load whats in EEPROM in to the local CONFIGURATION if it is a valid setting
  273. int loadConfig() {
  274.   if (EEPROM.read(CONFIG_START + 0) == CONFIG_VERSION[0] &&
  275.       EEPROM.read(CONFIG_START + 1) == CONFIG_VERSION[1] &&
  276.       EEPROM.read(CONFIG_START + 2) == CONFIG_VERSION[2] &&
  277.       EEPROM.read(CONFIG_START + 3) == CONFIG_VERSION[3] &&
  278.       EEPROM.read(CONFIG_START + 4) == CONFIG_VERSION[4]) {
  279.     // load (overwrite) the local configuration struct
  280.     for (unsigned int i = 0; i < sizeof(CONFIGURATION); i++) {
  281.       *((char*)&CONFIGURATION + i) = EEPROM.read(CONFIG_START + i);
  282.     }
  283.     return 1; // return 1 if config loaded
  284.   }
  285.   return 0; // return 0 if config NOT loaded
  286. }
  287. // save the CONFIGURATION in to EEPROM
  288. void saveConfig() {
  289.   CONFIGURATION.displaycolor = displaycolor;
  290.   CONFIGURATION.brightness = brightness;
  291.   for (unsigned int i = 0; i < sizeof(CONFIGURATION); i++)
  292.     EEPROM.write(CONFIG_START + i, *((char*)&CONFIGURATION + i));
  293. }
  294. // 1 display color level, 2 display brightness level, 3 both
  295. void showSettings(byte num, bool show) {
  296.   if (show) {
  297.     pixel.clear();
  298.     if (num == 1 || num == 3) {
  299.       drawChar(0, 0);
  300.       drawChar(displaycolor + 1, 5);
  301.     }
  302.     if (num == 2 || num == 3) {
  303.       drawChar(9, xres - 9);
  304.       drawChar(brightness + 1, xres - 4);
  305.     }
  306.     delay(1000);
  307.     pixel.clear();
  308.   }
  309. }
  310. // Draw custom chars
  311. void drawChar(byte val, byte pos) {
  312.   for (int x = 4; x >= 0; x--) {
  313.     for (int y = 5; y >= 0; y--) {
  314.       if ((charBitmap[val * 6 + 5 - y] >> x) & 0x01) {
  315.         pixel.setPixelColor(GetLedFromMatrix(4 - x + pos, y + 1), Wheel((pos > 10) * 170));
  316.         pixel.show();
  317.       }
  318.     }
  319.   }
  320. }
复制代码


回复

使用道具 举报

驴友花雕  中级技神
 楼主|

发表于 4 小时前

【Arduino 动手做】比FFT快4倍的FHT音频频谱可视化工具

【Arduino 动手做】比FFT快4倍的FHT音频频谱可视化工具
项目链接:https://www.hackster.io/janux/fh ... m-visualizer-83bba0
项目作者:贾努克斯

项目代码:https://www.hackster.io/code_files/459468/download

【Arduino 动手做】比FFT快4倍的FHT音频频谱可视化工具图1

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4 备案 沪公网安备31011502402448

© 2013-2025 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail