支持频谱显示,感谢哈哈哈群友的代码
This commit is contained in:
@@ -32,6 +32,9 @@ public:
|
||||
virtual std::string GetTheme() { return current_theme_name_; }
|
||||
virtual void UpdateStatusBar(bool update_all = false);
|
||||
virtual void SetPowerSaveMode(bool on);
|
||||
virtual void start() {}
|
||||
virtual void clearScreen() {} // 清除FFT显示,默认为空实现
|
||||
virtual void stopFft() {} // 停止FFT显示,默认为空实现
|
||||
|
||||
inline int width() const { return width_; }
|
||||
inline int height() const { return height_; }
|
||||
@@ -90,4 +93,4 @@ private:
|
||||
virtual void Unlock() override {}
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -4,17 +4,38 @@
|
||||
#include <algorithm>
|
||||
#include <font_awesome.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_random.h>
|
||||
#include <time.h>
|
||||
#include <esp_err.h>
|
||||
#include <esp_lvgl_port.h>
|
||||
#include <esp_heap_caps.h>
|
||||
#include "assets/lang_config.h"
|
||||
#include <cstring>
|
||||
#include <cmath>
|
||||
#include <math.h>
|
||||
#include "settings.h"
|
||||
|
||||
#include "board.h"
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
|
||||
#define TAG "LcdDisplay"
|
||||
|
||||
#define FFT_SIZE 512
|
||||
static int current_heights[40] = {0};
|
||||
static float avg_power_spectrum[FFT_SIZE/2]={-25.0f};
|
||||
|
||||
#define COLOR_BLACK 0x0000
|
||||
#define COLOR_RED 0xF800
|
||||
#define COLOR_GREEN 0x07E0
|
||||
#define COLOR_BLUE 0x001F
|
||||
#define COLOR_YELLOW 0xFFE0
|
||||
#define COLOR_CYAN 0x07FF
|
||||
#define COLOR_MAGENTA 0xF81F
|
||||
#define COLOR_WHITE 0xFFFF
|
||||
|
||||
// Color definitions for dark theme
|
||||
#define DARK_BACKGROUND_COLOR lv_color_hex(0x121212) // Dark background
|
||||
#define DARK_TEXT_COLOR lv_color_white() // White text
|
||||
@@ -105,7 +126,7 @@ SpiLcdDisplay::SpiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_h
|
||||
ESP_LOGI(TAG, "Initialize LVGL port");
|
||||
lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
|
||||
port_cfg.task_priority = 1;
|
||||
port_cfg.timer_period_ms = 40;
|
||||
port_cfg.timer_period_ms = 50;
|
||||
lvgl_port_init(&port_cfg);
|
||||
|
||||
ESP_LOGI(TAG, "Adding LCD display");
|
||||
@@ -145,6 +166,27 @@ SpiLcdDisplay::SpiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_h
|
||||
lv_display_set_offset(display_, offset_x, offset_y);
|
||||
}
|
||||
|
||||
// 初始化 FFT 相关内存
|
||||
fft_real = (float*)heap_caps_malloc(FFT_SIZE * sizeof(float), MALLOC_CAP_SPIRAM);
|
||||
fft_imag = (float*)heap_caps_malloc(FFT_SIZE * sizeof(float), MALLOC_CAP_SPIRAM);
|
||||
hanning_window_float = (float*)heap_caps_malloc(FFT_SIZE * sizeof(float), MALLOC_CAP_SPIRAM);
|
||||
|
||||
// 创建窗函数
|
||||
for (int i = 0; i < FFT_SIZE; i++) {
|
||||
hanning_window_float[i] = 0.5 * (1.0 - cos(2.0 * M_PI * i / (FFT_SIZE - 1)));
|
||||
}
|
||||
|
||||
if(audio_data==nullptr){
|
||||
audio_data=(int16_t*)heap_caps_malloc(sizeof(int16_t)*1152, MALLOC_CAP_SPIRAM);
|
||||
memset(audio_data,0,sizeof(int16_t)*1152);
|
||||
}
|
||||
if(frame_audio_data==nullptr){
|
||||
frame_audio_data=(int16_t*)heap_caps_malloc(sizeof(int16_t)*1152, MALLOC_CAP_SPIRAM);
|
||||
memset(frame_audio_data,0,sizeof(int16_t)*1152);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG,"Initialize fft_input, audio_data, frame_audio_data, spectrum_data");
|
||||
|
||||
SetupUI();
|
||||
}
|
||||
|
||||
@@ -270,6 +312,24 @@ MipiLcdDisplay::MipiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel
|
||||
}
|
||||
|
||||
LcdDisplay::~LcdDisplay() {
|
||||
// 停止FFT任务
|
||||
if (fft_task_handle != nullptr) {
|
||||
ESP_LOGI(TAG, "Stopping FFT task in destructor");
|
||||
fft_task_should_stop = true;
|
||||
|
||||
// 等待任务停止
|
||||
int wait_count = 0;
|
||||
while (fft_task_handle != nullptr && wait_count < 100) {
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
wait_count++;
|
||||
}
|
||||
|
||||
if (fft_task_handle != nullptr) {
|
||||
vTaskDelete(fft_task_handle);
|
||||
fft_task_handle = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// 然后再清理 LVGL 对象
|
||||
if (content_ != nullptr) {
|
||||
lv_obj_del(content_);
|
||||
@@ -367,7 +427,7 @@ void LcdDisplay::SetupUI() {
|
||||
emotion_label_ = lv_label_create(status_bar_);
|
||||
lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_4, 0);
|
||||
lv_obj_set_style_text_color(emotion_label_, current_theme_.text, 0);
|
||||
lv_label_set_text(emotion_label_, FONT_AWESOME_MICROCHIP_AI);
|
||||
lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP);
|
||||
lv_obj_set_style_margin_right(emotion_label_, 5, 0); // 添加右边距,与后面的元素分隔
|
||||
|
||||
notification_label_ = lv_label_create(status_bar_);
|
||||
@@ -814,11 +874,11 @@ void LcdDisplay::SetPreviewImage(const lv_img_dsc_t* img_dsc) {
|
||||
}
|
||||
|
||||
if (img_dsc != nullptr) {
|
||||
// 设置图片源并显示预览图片
|
||||
lv_image_set_src(preview_image_, img_dsc);
|
||||
// zoom factor 0.5
|
||||
lv_image_set_scale(preview_image_, 128 * width_ / img_dsc->header.w);
|
||||
lv_obj_remove_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
|
||||
// 设置图片源并显示预览图片
|
||||
lv_image_set_src(preview_image_, img_dsc);
|
||||
lv_obj_clear_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
|
||||
// 隐藏emotion_label_
|
||||
if (emotion_label_ != nullptr) {
|
||||
lv_obj_add_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
@@ -827,7 +887,7 @@ void LcdDisplay::SetPreviewImage(const lv_img_dsc_t* img_dsc) {
|
||||
// 隐藏预览图片并显示emotion_label_
|
||||
lv_obj_add_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
|
||||
if (emotion_label_ != nullptr) {
|
||||
lv_obj_remove_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_clear_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -867,13 +927,6 @@ void LcdDisplay::SetEmotion(const char* emotion) {
|
||||
std::string_view emotion_view(emotion);
|
||||
auto it = std::find_if(emotions.begin(), emotions.end(),
|
||||
[&emotion_view](const Emotion& e) { return e.text == emotion_view; });
|
||||
if (fonts_.emoji_font == nullptr || it == emotions.end()) {
|
||||
const char* utf8 = font_awesome_get_utf8(emotion);
|
||||
if (utf8 != nullptr) {
|
||||
SetIcon(utf8);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
DisplayLockGuard lock(this);
|
||||
if (emotion_label_ == nullptr) {
|
||||
@@ -890,7 +943,7 @@ void LcdDisplay::SetEmotion(const char* emotion) {
|
||||
|
||||
#if !CONFIG_USE_WECHAT_MESSAGE_STYLE
|
||||
// 显示emotion_label_,隐藏preview_image_
|
||||
lv_obj_remove_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_clear_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
if (preview_image_ != nullptr) {
|
||||
lv_obj_add_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
@@ -907,13 +960,43 @@ void LcdDisplay::SetIcon(const char* icon) {
|
||||
|
||||
#if !CONFIG_USE_WECHAT_MESSAGE_STYLE
|
||||
// 显示emotion_label_,隐藏preview_image_
|
||||
lv_obj_remove_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_clear_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
if (preview_image_ != nullptr) {
|
||||
lv_obj_add_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void LcdDisplay::SetMusicInfo(const char* song_name) {
|
||||
#if CONFIG_USE_WECHAT_MESSAGE_STYLE
|
||||
// 微信模式下不显示歌名,保持原有聊天功能
|
||||
return;
|
||||
#else
|
||||
// 非微信模式:在表情下方显示歌名
|
||||
DisplayLockGuard lock(this);
|
||||
if (chat_message_label_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (song_name != nullptr && strlen(song_name) > 0) {
|
||||
std::string music_text = "";
|
||||
music_text += song_name;
|
||||
lv_label_set_text(chat_message_label_, music_text.c_str());
|
||||
|
||||
// 确保显示 emotion_label_ 和 chat_message_label_,隐藏 preview_image_
|
||||
if (emotion_label_ != nullptr) {
|
||||
lv_obj_clear_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
if (preview_image_ != nullptr) {
|
||||
lv_obj_add_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
} else {
|
||||
// 清空歌名显示
|
||||
lv_label_set_text(chat_message_label_, "");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void LcdDisplay::SetTheme(const std::string& theme_name) {
|
||||
DisplayLockGuard lock(this);
|
||||
|
||||
@@ -1108,3 +1191,523 @@ void LcdDisplay::SetTheme(const std::string& theme_name) {
|
||||
// No errors occurred. Save theme to settings
|
||||
Display::SetTheme(theme_name);
|
||||
}
|
||||
|
||||
void LcdDisplay::create_canvas(){
|
||||
DisplayLockGuard lock(this);
|
||||
if (canvas_ != nullptr) {
|
||||
lv_obj_del(canvas_);
|
||||
}
|
||||
if (canvas_buffer_ != nullptr) {
|
||||
heap_caps_free(canvas_buffer_);
|
||||
canvas_buffer_ = nullptr;
|
||||
}
|
||||
|
||||
int status_bar_height=lv_obj_get_height(status_bar_);
|
||||
canvas_width_=width_;
|
||||
canvas_height_=height_-status_bar_height;
|
||||
|
||||
canvas_buffer_=(uint16_t*)heap_caps_malloc(canvas_width_ * canvas_height_ * sizeof(uint16_t), MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM);
|
||||
if (canvas_buffer_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to allocate canvas buffer");
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(TAG, "canvas buffer allocated successfully");
|
||||
canvas_ = lv_canvas_create(lv_scr_act());
|
||||
lv_canvas_set_buffer(canvas_, canvas_buffer_, canvas_width_, canvas_height_, LV_COLOR_FORMAT_RGB565);
|
||||
ESP_LOGI(TAG,"width: %d, height: %d", width_, height_);
|
||||
|
||||
|
||||
|
||||
lv_obj_set_pos(canvas_, 0, status_bar_height);
|
||||
lv_obj_set_size(canvas_, canvas_width_, canvas_height_);
|
||||
lv_canvas_fill_bg(canvas_, lv_color_make(0, 0, 0), LV_OPA_TRANSP);
|
||||
lv_obj_move_foreground(canvas_);
|
||||
|
||||
ESP_LOGI(TAG, "canvas created successfully");
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
void LcdDisplay::start(){
|
||||
ESP_LOGI(TAG, "Starting LcdDisplay with periodic data updates");
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
|
||||
// 创建周期性更新任务
|
||||
fft_task_should_stop = false; // 重置停止标志
|
||||
xTaskCreate(
|
||||
periodicUpdateTaskWrapper,
|
||||
"display_fft", // 任务名称
|
||||
4096*2, // 堆栈大小
|
||||
this, // 参数
|
||||
1, // 优先级
|
||||
&fft_task_handle // 保存到成员变量
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
|
||||
void LcdDisplay::drawSpectrumIfReady() {
|
||||
if (fft_data_ready) {
|
||||
draw_spectrum(avg_power_spectrum, FFT_SIZE/2);
|
||||
fft_data_ready = false;
|
||||
}
|
||||
}
|
||||
|
||||
void LcdDisplay::periodicUpdateTaskWrapper(void* arg) {
|
||||
auto self = static_cast<LcdDisplay*>(arg);
|
||||
self->periodicUpdateTask();
|
||||
}
|
||||
|
||||
void LcdDisplay::periodicUpdateTask() {
|
||||
ESP_LOGI(TAG, "Periodic update task started");
|
||||
|
||||
if(canvas_==nullptr){
|
||||
create_canvas();
|
||||
}
|
||||
else{
|
||||
ESP_LOGI(TAG, "canvas already created");
|
||||
}
|
||||
|
||||
|
||||
auto music = Board::GetInstance().GetMusic();
|
||||
|
||||
const TickType_t displayInterval = pdMS_TO_TICKS(40);
|
||||
const TickType_t audioProcessInterval = pdMS_TO_TICKS(15);
|
||||
|
||||
TickType_t lastDisplayTime = xTaskGetTickCount();
|
||||
TickType_t lastAudioTime = xTaskGetTickCount();
|
||||
|
||||
while (!fft_task_should_stop) {
|
||||
|
||||
TickType_t currentTime = xTaskGetTickCount();
|
||||
|
||||
|
||||
if (currentTime - lastAudioTime >= audioProcessInterval) {
|
||||
if(music->GetAudioData() != nullptr) {
|
||||
readAudioData(); // 快速处理,不阻塞
|
||||
} else {
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
lastAudioTime = currentTime;
|
||||
}
|
||||
|
||||
// 显示刷新(30Hz)
|
||||
if (currentTime - lastDisplayTime >= displayInterval) {
|
||||
if (fft_data_ready) {
|
||||
DisplayLockGuard lock(this);
|
||||
drawSpectrumIfReady();
|
||||
lv_area_t refresh_area;
|
||||
refresh_area.x1 = 0;
|
||||
refresh_area.y1 = height_-100;
|
||||
refresh_area.x2 = canvas_width_ -1;
|
||||
refresh_area.y2 = height_ -1; // 只刷新频谱区域
|
||||
lv_obj_invalidate_area(canvas_, &refresh_area);
|
||||
//lv_obj_invalidate(canvas_);
|
||||
fft_data_ready = false;
|
||||
lastDisplayTime = currentTime;
|
||||
} // 绘制操作
|
||||
|
||||
|
||||
// 更新FPS计数
|
||||
//FPS();
|
||||
}
|
||||
|
||||
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
// 短暂延迟
|
||||
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "FFT display task stopped");
|
||||
fft_task_handle = nullptr; // 清空任务句柄
|
||||
vTaskDelete(NULL); // 删除当前任务
|
||||
}
|
||||
|
||||
|
||||
|
||||
void LcdDisplay::readAudioData(){
|
||||
|
||||
auto music = Board::GetInstance().GetMusic();
|
||||
|
||||
if(music->GetAudioData()!=nullptr){
|
||||
|
||||
|
||||
if(audio_display_last_update<=2){
|
||||
memcpy(audio_data,music->GetAudioData(),sizeof(int16_t)*1152);
|
||||
for(int i=0;i<1152;i++){
|
||||
frame_audio_data[i]+=audio_data[i];
|
||||
}
|
||||
audio_display_last_update++;
|
||||
|
||||
}else{
|
||||
const int HOP_SIZE = 512;
|
||||
const int NUM_SEGMENTS = 1 + (1152 - FFT_SIZE) / HOP_SIZE;
|
||||
|
||||
for (int seg = 0; seg < NUM_SEGMENTS; seg++) {
|
||||
int start = seg * HOP_SIZE;
|
||||
if (start + FFT_SIZE > 1152) break;
|
||||
|
||||
// 准备当前段数据
|
||||
for (int i = 0; i < FFT_SIZE; i++) {
|
||||
int idx = start + i;
|
||||
//float sample =frame_audio_data[idx] / 32768.0f;
|
||||
float sample =frame_audio_data[idx] / 32768.0f;
|
||||
fft_real[i] = sample * hanning_window_float[i];
|
||||
fft_imag[i] = 0.0f;
|
||||
|
||||
}
|
||||
|
||||
compute(fft_real, fft_imag, FFT_SIZE, true);
|
||||
|
||||
// 计算功率谱并累加(双边)
|
||||
for (int i = 0; i < FFT_SIZE/2; i++) {
|
||||
avg_power_spectrum[i] += fft_real[i] * fft_real[i]+fft_imag[i] * fft_imag[i]; // 功率 = 幅度平方
|
||||
}
|
||||
}
|
||||
|
||||
// 计算平均值
|
||||
for (int i = 0; i < FFT_SIZE/2; i++) {
|
||||
avg_power_spectrum[i] /= NUM_SEGMENTS;
|
||||
}
|
||||
|
||||
audio_display_last_update=0;
|
||||
//memcpy(spectrum_data, avg_power_spectrum, sizeof(float) * FFT_SIZE/2);
|
||||
fft_data_ready=true;
|
||||
|
||||
//draw_spectrum(avg_power_spectrum, FFT_SIZE/2);
|
||||
memset(frame_audio_data,0,sizeof(int16_t)*1152);
|
||||
|
||||
}
|
||||
}else{
|
||||
ESP_LOGI(TAG, "audio_data is nullptr");
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t LcdDisplay::get_bar_color(int x_pos){
|
||||
|
||||
static uint16_t color_table[40];
|
||||
static bool initialized = false;
|
||||
|
||||
if (!initialized) {
|
||||
// 生成黄绿->黄->黄红的渐变
|
||||
for (int i = 0; i < 40; i++) {
|
||||
if (i < 20) {
|
||||
// 黄绿到黄:增加红色分量
|
||||
uint8_t r = static_cast<uint8_t>((i / 19.0f) * 31);
|
||||
color_table[i] = (r << 11) | (0x3F << 5);
|
||||
} else {
|
||||
// 黄到黄红:减少绿色分量
|
||||
uint8_t g = static_cast<uint8_t>((1.0f - (i - 20) / 19.0f * 0.5f) * 63);
|
||||
color_table[i] = (0x1F << 11) | (g << 5);
|
||||
}
|
||||
}
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
return color_table[x_pos];
|
||||
}
|
||||
|
||||
|
||||
void LcdDisplay::draw_spectrum(float *power_spectrum,int fft_size){
|
||||
|
||||
|
||||
const int bartotal=40;
|
||||
int bar_height;
|
||||
const int bar_max_height=canvas_height_-100;
|
||||
const int bar_width=240/bartotal;
|
||||
int x_pos=0;
|
||||
int y_pos = (canvas_height_) - 1;
|
||||
|
||||
float magnitude[bartotal]={0};
|
||||
float max_magnitude=0;
|
||||
|
||||
const float MIN_DB = -25.0f;
|
||||
const float MAX_DB = 0.0f;
|
||||
|
||||
for (int bin = 0; bin < bartotal; bin++) {
|
||||
int start = bin * (fft_size / bartotal);
|
||||
int end = (bin+1) * (fft_size / bartotal);
|
||||
magnitude[bin] = 0;
|
||||
int count=0;
|
||||
for (int k = start; k < end; k++) {
|
||||
magnitude[bin] += sqrt(power_spectrum[k]);
|
||||
count++;
|
||||
}
|
||||
if(count>0){
|
||||
magnitude[bin] /= count;
|
||||
}
|
||||
|
||||
|
||||
if (magnitude[bin] > max_magnitude) max_magnitude = magnitude[bin];
|
||||
}
|
||||
|
||||
|
||||
magnitude[1]=magnitude[1]*0.6;
|
||||
magnitude[2]=magnitude[2]*0.7;
|
||||
magnitude[3]=magnitude[3]*0.8;
|
||||
magnitude[4]=magnitude[4]*0.8;
|
||||
magnitude[5]=magnitude[5]*0.9;
|
||||
/*
|
||||
if (bartotal >= 6) {
|
||||
magnitude[0] *= 0.3f; // 最低频
|
||||
magnitude[1] *= 1.1f;
|
||||
magnitude[2] *= 1.0f;
|
||||
magnitude[3] *= 0.9f;
|
||||
magnitude[4] *= 0.8f;
|
||||
magnitude[5] *= 0.7f;
|
||||
// 更高频率保持或进一步衰减
|
||||
for (int i = 6; i < bartotal; i++) {
|
||||
magnitude[i] *= 0.6f;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
for (int bin = 1; bin < bartotal; bin++) {
|
||||
|
||||
if (magnitude[bin] > 0.0f && max_magnitude > 0.0f) {
|
||||
// 相对dB值:20 * log10(magnitude/ref_level)
|
||||
magnitude[bin] = 20.0f * log10f(magnitude[bin] / max_magnitude+ 1e-10);
|
||||
} else {
|
||||
magnitude[bin] = MIN_DB;
|
||||
}
|
||||
if (magnitude[bin] > max_magnitude) max_magnitude = magnitude[bin];
|
||||
}
|
||||
|
||||
clearScreen();
|
||||
|
||||
for (int k = 1; k < bartotal; k++) { // 跳过直流分量(k=0)
|
||||
x_pos=canvas_width_/bartotal*(k-1);
|
||||
float mag=(magnitude[k] - MIN_DB) / (MAX_DB - MIN_DB);
|
||||
mag = std::max(0.0f, std::min(1.0f, mag));
|
||||
bar_height=int(mag*(bar_max_height));
|
||||
|
||||
int color=get_bar_color(k);
|
||||
draw_bar(x_pos,y_pos,bar_width,bar_height, color,k-1);
|
||||
//printf("x: %d, y: %d,\n", x_pos, bar_height);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void LcdDisplay::draw_bar(int x,int y,int bar_width,int bar_height,uint16_t color,int bar_index){
|
||||
|
||||
const int block_space=2;
|
||||
const int block_x_size=bar_width-block_space;
|
||||
const int block_y_size=4;
|
||||
|
||||
int blocks_per_col=(bar_height/(block_y_size+block_space));
|
||||
int start_x=(block_x_size+block_space)/2+x;
|
||||
|
||||
if(current_heights[bar_index]<bar_height)
|
||||
{
|
||||
current_heights[bar_index]=bar_height;
|
||||
}
|
||||
else{
|
||||
int fall_speed=2;
|
||||
current_heights[bar_index]=current_heights[bar_index]-fall_speed;
|
||||
if(current_heights[bar_index]>(block_y_size+block_space))
|
||||
draw_block(start_x,canvas_height_-current_heights[bar_index],block_x_size,block_y_size,color,bar_index);
|
||||
|
||||
}
|
||||
|
||||
draw_block(start_x,canvas_height_-1,block_x_size,block_y_size,color,bar_index);
|
||||
|
||||
for(int j=1;j<blocks_per_col;j++){
|
||||
|
||||
int start_y=j*(block_y_size+block_space);
|
||||
draw_block(start_x,canvas_height_-start_y,block_x_size,block_y_size,color,bar_index);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void LcdDisplay::draw_block(int x,int y,int block_x_size,int block_y_size,uint16_t color,int bar_index){
|
||||
|
||||
/*
|
||||
for(int dy=0;dy<block_y_size;dy++){
|
||||
for(int dx=0;dx<block_x_size;dx++){
|
||||
canvas_buffer_[(y-dy)*canvas_width_+x+dx]=color;
|
||||
}
|
||||
}
|
||||
*/
|
||||
for (int row = y; row > y-block_y_size;row--) {
|
||||
// 一次绘制一行
|
||||
uint16_t* line_start = &canvas_buffer_[row * canvas_width_ + x];
|
||||
std::fill_n(line_start, block_x_size, color);
|
||||
}
|
||||
}
|
||||
|
||||
void LcdDisplay::clearScreen() {
|
||||
// DisplayLockGuard lock(this);
|
||||
// 清屏为黑色
|
||||
//for (int i = 0; i < canvas_width_ * canvas_height_; i++) {
|
||||
// canvas_buffer_[i] = COLOR_BLACK;
|
||||
//}
|
||||
//lv_obj_invalidate(canvas_);
|
||||
std::fill_n(canvas_buffer_, canvas_width_ * canvas_height_, COLOR_BLACK);
|
||||
|
||||
}
|
||||
|
||||
void LcdDisplay::stopFft() {
|
||||
ESP_LOGI(TAG, "Stopping FFT display");
|
||||
|
||||
// 停止FFT显示任务
|
||||
if (fft_task_handle != nullptr) {
|
||||
ESP_LOGI(TAG, "Stopping FFT display task");
|
||||
fft_task_should_stop = true; // 设置停止标志
|
||||
|
||||
// 等待任务停止(最多等待1秒)
|
||||
int wait_count = 0;
|
||||
while (fft_task_handle != nullptr && wait_count < 100) {
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
wait_count++;
|
||||
}
|
||||
|
||||
if (fft_task_handle != nullptr) {
|
||||
ESP_LOGW(TAG, "FFT task did not stop gracefully, force deleting");
|
||||
vTaskDelete(fft_task_handle);
|
||||
fft_task_handle = nullptr;
|
||||
} else {
|
||||
ESP_LOGI(TAG, "FFT display task stopped successfully");
|
||||
}
|
||||
}
|
||||
|
||||
// 使用显示锁保护所有操作
|
||||
DisplayLockGuard lock(this);
|
||||
|
||||
// 重置FFT状态变量
|
||||
fft_data_ready = false;
|
||||
audio_display_last_update = 0;
|
||||
|
||||
// 重置频谱条高度
|
||||
memset(current_heights, 0, sizeof(current_heights));
|
||||
|
||||
// 重置平均功率谱数据
|
||||
for (int i = 0; i < FFT_SIZE/2; i++) {
|
||||
avg_power_spectrum[i] = -25.0f;
|
||||
}
|
||||
|
||||
// 删除FFT画布对象,让原始UI重新显示
|
||||
if (canvas_ != nullptr) {
|
||||
lv_obj_del(canvas_);
|
||||
canvas_ = nullptr;
|
||||
ESP_LOGI(TAG, "FFT canvas deleted");
|
||||
}
|
||||
|
||||
// 释放画布缓冲区内存
|
||||
if (canvas_buffer_ != nullptr) {
|
||||
heap_caps_free(canvas_buffer_);
|
||||
canvas_buffer_ = nullptr;
|
||||
ESP_LOGI(TAG, "FFT canvas buffer freed");
|
||||
}
|
||||
|
||||
// 重置画布尺寸变量
|
||||
canvas_width_ = 0;
|
||||
canvas_height_ = 0;
|
||||
|
||||
ESP_LOGI(TAG, "FFT display stopped, original UI restored");
|
||||
}
|
||||
|
||||
void LcdDisplay::MyUI(){
|
||||
|
||||
DisplayLockGuard lock(this);
|
||||
|
||||
auto screen = lv_screen_active();
|
||||
lv_obj_set_style_text_font(screen, fonts_.text_font, 0);
|
||||
lv_obj_set_style_text_color(screen, lv_color_white(), 0);
|
||||
lv_obj_set_style_bg_color(screen, lv_color_black(), 0);
|
||||
|
||||
/* Container */
|
||||
container_ = lv_obj_create(screen);
|
||||
lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES);
|
||||
lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_all(container_, 0, 0);
|
||||
lv_obj_set_style_border_width(container_, 0, 0);
|
||||
lv_obj_set_style_pad_row(container_, 0, 0);
|
||||
lv_obj_set_style_bg_color(container_, lv_color_black(), 0);
|
||||
lv_obj_set_style_border_color(container_, lv_color_white(), 0);
|
||||
|
||||
|
||||
/* Status bar */
|
||||
status_bar_ = lv_obj_create(container_);
|
||||
lv_obj_set_size(status_bar_, LV_HOR_RES, fonts_.text_font->line_height);
|
||||
lv_obj_set_style_radius(status_bar_, 0, 0);
|
||||
lv_obj_set_style_bg_color(status_bar_, lv_color_black(), 0);
|
||||
lv_obj_set_style_text_color(status_bar_, lv_color_white(), 0);
|
||||
|
||||
/* Status bar */
|
||||
lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_style_pad_all(status_bar_, 0, 0);
|
||||
lv_obj_set_style_border_width(status_bar_, 0, 0);
|
||||
lv_obj_set_style_pad_column(status_bar_, 0, 0);
|
||||
lv_obj_set_style_pad_left(status_bar_, 2, 0);
|
||||
lv_obj_set_style_pad_right(status_bar_, 2, 0);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
void LcdDisplay::compute(float* real, float* imag, int n, bool forward) {
|
||||
// 位反转排序
|
||||
int j = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (j > i) {
|
||||
std::swap(real[i], real[j]);
|
||||
std::swap(imag[i], imag[j]);
|
||||
}
|
||||
|
||||
int m = n >> 1;
|
||||
while (m >= 1 && j >= m) {
|
||||
j -= m;
|
||||
m >>= 1;
|
||||
}
|
||||
j += m;
|
||||
}
|
||||
|
||||
// FFT计算
|
||||
for (int s = 1; s <= (int)log2(n); s++) {
|
||||
int m = 1 << s;
|
||||
int m2 = m >> 1;
|
||||
float w_real = 1.0f;
|
||||
float w_imag = 0.0f;
|
||||
float angle = (forward ? -2.0f : 2.0f) * M_PI / m;
|
||||
float wm_real = cosf(angle);
|
||||
float wm_imag = sinf(angle);
|
||||
|
||||
for (int j = 0; j < m2; j++) {
|
||||
for (int k = j; k < n; k += m) {
|
||||
int k2 = k + m2;
|
||||
float t_real = w_real * real[k2] - w_imag * imag[k2];
|
||||
float t_imag = w_real * imag[k2] + w_imag * real[k2];
|
||||
|
||||
real[k2] = real[k] - t_real;
|
||||
imag[k2] = imag[k] - t_imag;
|
||||
real[k] += t_real;
|
||||
imag[k] += t_imag;
|
||||
}
|
||||
|
||||
float w_temp = w_real;
|
||||
w_real = w_real * wm_real - w_imag * wm_imag;
|
||||
w_imag = w_temp * wm_imag + w_imag * wm_real;
|
||||
}
|
||||
}
|
||||
|
||||
// 正向变换需要缩放
|
||||
if (forward) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
real[i] /= n;
|
||||
imag[i] /= n;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,6 +8,11 @@
|
||||
#include <font_emoji.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
#include <esp_timer.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <freertos/event_groups.h>
|
||||
|
||||
// Theme color structure
|
||||
struct ThemeColors {
|
||||
@@ -42,6 +47,53 @@ protected:
|
||||
virtual bool Lock(int timeout_ms = 0) override;
|
||||
virtual void Unlock() override;
|
||||
|
||||
// FFT 绘制方法
|
||||
void readAudioData();
|
||||
|
||||
|
||||
|
||||
virtual void clearScreen() override;
|
||||
virtual void stopFft() override; // 停止FFT显示
|
||||
|
||||
// 定时任务方法
|
||||
void periodicUpdateTask();
|
||||
static void periodicUpdateTaskWrapper(void* arg);
|
||||
|
||||
// LVGL变量
|
||||
lv_obj_t* canvas_ = nullptr;
|
||||
uint16_t* canvas_buffer_ = nullptr;
|
||||
void create_canvas();
|
||||
uint16_t get_bar_color(int x_pos);
|
||||
void draw_spectrum(float *power_spectrum,int fft_size);
|
||||
void draw_bar(int x,int y,int bar_width,int bar_height,uint16_t color,int bar_index);
|
||||
void draw_block(int x,int y,int block_x_size,int block_y_size,uint16_t color,int bar_index);
|
||||
|
||||
int canvas_width_;
|
||||
int canvas_height_;
|
||||
|
||||
|
||||
int16_t* audio_data=nullptr;
|
||||
int16_t* frame_audio_data=nullptr;
|
||||
uint32_t last_fft_update = 0;
|
||||
bool fft_data_ready = false;
|
||||
float* spectrum_data=nullptr;
|
||||
|
||||
// FFT 相关变量
|
||||
int audio_display_last_update = 0;
|
||||
std::atomic<bool> fft_task_should_stop = false; // FFT任务停止标志
|
||||
TaskHandle_t fft_task_handle = nullptr; // FFT任务句柄
|
||||
|
||||
float* fft_real;
|
||||
float* fft_imag;
|
||||
float* hanning_window_float;
|
||||
void compute(float* real, float* imag, int n, bool forward);
|
||||
|
||||
// 添加缺少的方法声明
|
||||
void drawSpectrumIfReady();
|
||||
void MyUI();
|
||||
|
||||
|
||||
|
||||
protected:
|
||||
// 添加protected构造函数
|
||||
LcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, DisplayFonts fonts, int width, int height);
|
||||
@@ -50,6 +102,7 @@ public:
|
||||
~LcdDisplay();
|
||||
virtual void SetEmotion(const char* emotion) override;
|
||||
virtual void SetIcon(const char* icon) override;
|
||||
virtual void SetMusicInfo(const char* song_name) override;
|
||||
virtual void SetPreviewImage(const lv_img_dsc_t* img_dsc) override;
|
||||
#if CONFIG_USE_WECHAT_MESSAGE_STYLE
|
||||
virtual void SetChatMessage(const char* role, const char* content) override;
|
||||
@@ -57,6 +110,8 @@ public:
|
||||
|
||||
// Add theme switching function
|
||||
virtual void SetTheme(const std::string& theme_name) override;
|
||||
virtual void start() override;
|
||||
|
||||
};
|
||||
|
||||
// RGB LCD显示器
|
||||
@@ -103,4 +158,4 @@ public:
|
||||
bool mirror_x, bool mirror_y, bool swap_xy,
|
||||
DisplayFonts fonts);
|
||||
};
|
||||
#endif // LCD_DISPLAY_H
|
||||
#endif // LCD_DISPLAY_H
|
||||
Reference in New Issue
Block a user