From 971b905e7737b77c99e389f56f264dbc86c07ba2 Mon Sep 17 00:00:00 2001 From: TimLiu Date: Fri, 22 May 2026 15:31:56 +0800 Subject: [PATCH 1/2] [OpenClaw CTO Bot] feat: Convert 2D Bar Graph to 3D Bar Graph visualization Fixes #1 - Added Bar3DVisualization with perspective projection - X-axis: frequencies, Y-axis: amplitudes, Z-axis: time variation - 3D box rendering with 6-face shading (top brighter, bottom darker) - Auto-rotation + interactive mouse drag rotation - Scroll zoom support - History buffer (deque) for time-series FFT data - New vertexShader3D.glsl with mat4 MVP uniform - Added option 5 in main.cpp menu - Depth testing enabled for proper 3D occlusion --- src/main.cpp | 5 + src/visualizations/Bar3DVisualization.cpp | 299 ++++++++++++++++++++++ src/visualizations/Bar3DVisualization.h | 58 +++++ src/visualizations/_generation.json | 1 + src/visualizations/vertexShader3D.glsl | 12 + 5 files changed, 375 insertions(+) create mode 100644 src/visualizations/Bar3DVisualization.cpp create mode 100644 src/visualizations/Bar3DVisualization.h create mode 100644 src/visualizations/_generation.json create mode 100644 src/visualizations/vertexShader3D.glsl diff --git a/src/main.cpp b/src/main.cpp index 1acefc5..3015c88 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,6 +4,7 @@ #include "visualizations/BarVisualization.h" #include "visualizations/CircularBarVisualization.h" #include "visualizations/MountainVisualization.h" +#include "visualizations/Bar3DVisualization.h" #include #include @@ -25,6 +26,7 @@ int main() { cout << "2. Bar Visualization\n"; cout << "3. Circular Bar Visualization\n"; cout << "4. Mountain Visualization\n"; + cout << "5. 3D Bar Visualization\n"; cout << "Enter choice: "; cin >> choice; @@ -43,6 +45,9 @@ int main() { case 4: visualization = make_unique(); break; + case 5: + visualization = make_unique(); + break; default: cout << "Invalid choice. Exiting.\n"; return -1; diff --git a/src/visualizations/Bar3DVisualization.cpp b/src/visualizations/Bar3DVisualization.cpp new file mode 100644 index 0000000..37ed211 --- /dev/null +++ b/src/visualizations/Bar3DVisualization.cpp @@ -0,0 +1,299 @@ +#define _USE_MATH_DEFINES +#include "Bar3DVisualization.h" +#include +#include +#include + +// ─── Constructor / Destructor ─────────────────────────────────────────────── + +Bar3DVisualization::Bar3DVisualization() + : window(nullptr), vbo(0), vao(0), shaderProgram(0), + cameraAngleX(25.0f), cameraAngleY(-35.0f), cameraDistance(8.0f), autoRotation(0.0f), + mouseDragging(false), lastMouseX(0), lastMouseY(0), + smoothedFFT(128, 0.0f) {} + +Bar3DVisualization::~Bar3DVisualization() { cleanup(); } + +// ─── Callbacks ────────────────────────────────────────────────────────────── + +void Bar3DVisualization::framebufferSizeCallback(GLFWwindow* window, int width, int height) { + glViewport(0, 0, width, height); +} + +void Bar3DVisualization::mouseButtonCallback(GLFWwindow* window, int button, int action, int mods) { + auto* self = static_cast(glfwGetWindowUserPointer(window)); + if (!self) return; + if (button == GLFW_MOUSE_BUTTON_LEFT) { + self->mouseDragging = (action == GLFW_PRESS); + glfwGetCursorPos(window, &self->lastMouseX, &self->lastMouseY); + } +} + +void Bar3DVisualization::cursorPosCallback(GLFWwindow* window, double xpos, double ypos) { + auto* self = static_cast(glfwGetWindowUserPointer(window)); + if (!self || !self->mouseDragging) return; + double dx = xpos - self->lastMouseX; + double dy = ypos - self->lastMouseY; + self->cameraAngleY += (float)dx * 0.3f; + self->cameraAngleX += (float)dy * 0.3f; + self->cameraAngleX = std::clamp(self->cameraAngleX, -89.0f, 89.0f); + self->lastMouseX = xpos; + self->lastMouseY = ypos; +} + +void Bar3DVisualization::scrollCallback(GLFWwindow* window, double xoffset, double yoffset) { + auto* self = static_cast(glfwGetWindowUserPointer(window)); + if (!self) return; + self->cameraDistance -= (float)yoffset * 0.5f; + self->cameraDistance = std::clamp(self->cameraDistance, 3.0f, 20.0f); +} + +// ─── Matrix Helpers ───────────────────────────────────────────────────────── + +std::vector Bar3DVisualization::perspectiveMatrix(float fov, float aspect, float nearP, float farP) { + float f = 1.0f / tanf(fov * 0.5f * (float)M_PI / 180.0f); + float rangeInv = 1.0f / (nearP - farP); + return { + f / aspect, 0, 0, 0, + 0, f, 0, 0, + 0, 0, (nearP + farP) * rangeInv, -1, + 0, 0, 2.0f * nearP * farP * rangeInv, 0 + }; +} + +std::vector Bar3DVisualization::lookAtMatrix(float eyeX, float eyeY, float eyeZ, + float centerX, float centerY, float centerZ, + float upX, float upY, float upZ) { + float fX = centerX - eyeX, fY = centerY - eyeY, fZ = centerZ - eyeZ; + float fLen = sqrtf(fX * fX + fY * fY + fZ * fZ); + fX /= fLen; fY /= fLen; fZ /= fLen; + + float sX = fY * upZ - fZ * upY; + float sY = fZ * upX - fX * upZ; + float sZ = fX * upY - fY * upX; + float sLen = sqrtf(sX * sX + sY * sY + sZ * sZ); + sX /= sLen; sY /= sLen; sZ /= sLen; + + float uX = sY * fZ - sZ * fY; + float uY = sZ * fX - sX * fZ; + float uZ = sX * fY - sY * fX; + + return { + sX, uX, -fX, 0, + sY, uY, -fY, 0, + sZ, uZ, -fZ, 0, + -(sX * eyeX + sY * eyeY + sZ * eyeZ), + -(uX * eyeX + uY * eyeY + uZ * eyeZ), + (fX * eyeX + fY * eyeY + fZ * eyeZ), + 1 + }; +} + +std::vector Bar3DVisualization::multiplyMat4(const std::vector& a, const std::vector& b) { + std::vector result(16, 0.0f); + for (int col = 0; col < 4; col++) { + for (int row = 0; row < 4; row++) { + for (int k = 0; k < 4; k++) { + result[col * 4 + row] += a[k * 4 + row] * b[col * 4 + k]; + } + } + } + return result; +} + +// ─── Initialize ───────────────────────────────────────────────────────────── + +bool Bar3DVisualization::initialize(int windowWidth, int windowHeight) { + if (!glfwInit()) { + std::cerr << "Failed to initialize GLFW." << std::endl; + return false; + } + + window = glfwCreateWindow(windowWidth, windowHeight, "3D Bar Visualization", nullptr, nullptr); + if (!window) { + std::cerr << "Failed to create GLFW window." << std::endl; + glfwTerminate(); + return false; + } + + glfwMakeContextCurrent(window); + glewExperimental = GL_TRUE; + if (glewInit() != GLEW_OK) { + std::cerr << "Failed to initialize GLEW." << std::endl; + return false; + } + + glfwSetWindowUserPointer(window, this); + glfwSetFramebufferSizeCallback(window, framebufferSizeCallback); + glfwSetMouseButtonCallback(window, mouseButtonCallback); + glfwSetCursorPosCallback(window, cursorPosCallback); + glfwSetScrollCallback(window, scrollCallback); + + // Enable depth testing for 3D + glEnable(GL_DEPTH_TEST); + + glGenVertexArrays(1, &vao); + glGenBuffers(1, &vbo); + + glBindVertexArray(vao); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + + // Position: location 0 (3 components for 3D) + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0); + glEnableVertexAttribArray(0); + + // Color: location 1 + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float))); + glEnableVertexAttribArray(1); + + glClearColor(0.05f, 0.05f, 0.1f, 1.0f); + + // Use 3D shaders + shaderProgram = createShaderProgram("visualizations/vertexShader3D.glsl", "visualizations/fragmentShader.glsl"); + if (shaderProgram == 0) { + std::cerr << "ERROR: Failed to create 3D shader program!" << std::endl; + return false; + } + + glUseProgram(shaderProgram); + + // Pre-fill history with empty data + std::vector empty(64, 0.0f); + for (size_t i = 0; i < HISTORY_LENGTH; ++i) { + fftHistory.push_front(empty); + } + + return true; +} + +// ─── Render ───────────────────────────────────────────────────────────────── + +void Bar3DVisualization::render(const std::vector& fftMagnitudes) { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glUseProgram(shaderProgram); + + if (fftMagnitudes.empty()) return; + + // Number of frequency bars on x-axis + size_t numBars = fftMagnitudes.size() / 8; + if (numBars < 1) numBars = 1; + + // Ensure smoothedFFT is the right size + if (smoothedFFT.size() != numBars) smoothedFFT.resize(numBars, 0.0f); + + // ── Smooth current FFT data ── + float maxMagnitude = 0.0f; + for (size_t i = 0; i < numBars; ++i) { + if (i < fftMagnitudes.size() && fftMagnitudes[i] > maxMagnitude) + maxMagnitude = fftMagnitudes[i]; + } + if (maxMagnitude < 1e-6f) maxMagnitude = 1.0f; + + float decayFactor = 0.9f; + for (size_t i = 0; i < numBars; ++i) { + float rawMag = (i < fftMagnitudes.size()) ? fftMagnitudes[i] : 0.0f; + float normalizedMag = rawMag / maxMagnitude; + float logMag = log10(1.0f + normalizedMag * 10.0f) / log10(2.0f); + smoothedFFT[i] = smoothedFFT[i] * decayFactor + (1.0f - decayFactor) * logMag; + } + + // Push to history (newest at front) + fftHistory.push_front(smoothedFFT); + if (fftHistory.size() > HISTORY_LENGTH) fftHistory.pop_back(); + + // ── Build 3D bar vertices ── + std::vector vertices; + float barWidth = 0.7f; + float barDepth = 0.7f; + float spacing = 0.3f; + float zSpacing = 1.2f; + + for (size_t z = 0; z < fftHistory.size(); ++z) { + const auto& frame = fftHistory[z]; + float zPos = -(float)z * zSpacing; + + for (size_t i = 0; i < numBars && i < frame.size(); ++i) { + float height = frame[i] * 2.5f; + if (height < 0.02f) height = 0.02f; + + float xPos = (float)i * (barWidth + spacing) - (numBars * (barWidth + spacing)) * 0.5f; + float yPos = 0.0f; + + // Color: unique per frequency, fading with z-distance + Color color = getColorFromMagnitude(frame[i], 0.0f, 1.0f); + float zFade = 1.0f - ((float)z / (float)HISTORY_LENGTH) * 0.5f; + float cr = color.r * zFade; + float cg = color.g * zFade; + float cb = color.b * zFade; + + // Build 6 faces of a 3D box + float x0 = xPos, x1 = xPos + barWidth; + float y0 = yPos, y1 = yPos + height; + float z0 = zPos, z1 = zPos + barDepth; + + // Front face + vertices.insert(vertices.end(), { x0,y0,z1, cr,cg,cb, x1,y0,z1, cr,cg,cb, x0,y1,z1, cr,cg,cb, + x1,y0,z1, cr,cg,cb, x1,y1,z1, cr,cg,cb, x0,y1,z1, cr,cg,cb }); + // Back face + vertices.insert(vertices.end(), { x1,y0,z0, cr,cg,cb, x0,y0,z0, cr,cg,cb, x1,y1,z0, cr,cg,cb, + x0,y0,z0, cr,cg,cb, x0,y1,z0, cr,cg,cb, x1,y1,z0, cr,cg,cb }); + // Left face + vertices.insert(vertices.end(), { x0,y0,z0, cr,cg,cb, x0,y0,z1, cr,cg,cb, x0,y1,z0, cr,cg,cb, + x0,y0,z1, cr,cg,cb, x0,y1,z1, cr,cg,cb, x0,y1,z0, cr,cg,cb }); + // Right face + vertices.insert(vertices.end(), { x1,y0,z1, cr,cg,cb, x1,y0,z0, cr,cg,cb, x1,y1,z1, cr,cg,cb, + x1,y0,z0, cr,cg,cb, x1,y1,z0, cr,cg,cb, x1,y1,z1, cr,cg,cb }); + // Top face (brighter) + float tr = std::min(cr * 1.3f, 1.0f), tg = std::min(cg * 1.3f, 1.0f), tb = std::min(cb * 1.3f, 1.0f); + vertices.insert(vertices.end(), { x0,y1,z1, tr,tg,tb, x1,y1,z1, tr,tg,tb, x0,y1,z0, tr,tg,tb, + x1,y1,z1, tr,tg,tb, x1,y1,z0, tr,tg,tb, x0,y1,z0, tr,tg,tb }); + // Bottom face + float br2 = cr * 0.4f, bg2 = cg * 0.4f, bb2 = cb * 0.4f; + vertices.insert(vertices.end(), { x0,y0,z0, br2,bg2,bb2, x1,y0,z0, br2,bg2,bb2, x0,y0,z1, br2,bg2,bb2, + x1,y0,z0, br2,bg2,bb2, x1,y0,z1, br2,bg2,bb2, x0,y0,z1, br2,bg2,bb2 }); + } + } + + // ── Camera setup ── + autoRotation += 0.15f; + float totalAngleY = cameraAngleY + autoRotation; + float radX = cameraAngleX * (float)M_PI / 180.0f; + float radY = totalAngleY * (float)M_PI / 180.0f; + + float eyeX = cameraDistance * cosf(radX) * sinf(radY); + float eyeY = cameraDistance * sinf(radX) + 2.0f; + float eyeZ = cameraDistance * cosf(radX) * cosf(radY); + + int width, height; + glfwGetFramebufferSize(window, &width, &height); + float aspect = (height > 0) ? (float)width / (float)height : 1.0f; + + auto proj = perspectiveMatrix(45.0f, aspect, 0.1f, 100.0f); + auto view = lookAtMatrix(eyeX, eyeY, eyeZ, 0, 1.0f, -HISTORY_LENGTH * 0.5f, 0, 1, 0); + auto mvp = multiplyMat4(proj, view); + + // Upload MVP matrix + GLint mvpLoc = glGetUniformLocation(shaderProgram, "uMVP"); + glUniformMatrix4fv(mvpLoc, 1, GL_FALSE, mvp.data()); + + // Draw + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_DYNAMIC_DRAW); + + glBindVertexArray(vao); + glDrawArrays(GL_TRIANGLES, 0, (GLsizei)(vertices.size() / 6)); + + glfwSwapBuffers(window); + glfwPollEvents(); +} + +// ─── Should Close / Cleanup ───────────────────────────────────────────────── + +bool Bar3DVisualization::shouldClose() { return glfwWindowShouldClose(window); } + +void Bar3DVisualization::cleanup() { + if (vbo != 0) glDeleteBuffers(1, &vbo); + if (vao != 0) glDeleteVertexArrays(1, &vao); + if (window) glfwDestroyWindow(window); + glfwTerminate(); +} diff --git a/src/visualizations/Bar3DVisualization.h b/src/visualizations/Bar3DVisualization.h new file mode 100644 index 0000000..008fca8 --- /dev/null +++ b/src/visualizations/Bar3DVisualization.h @@ -0,0 +1,58 @@ +#ifndef BAR3D_VISUALIZATION_H +#define BAR3D_VISUALIZATION_H + +#include "..\ShaderUtils.h" +#include "BaseVisualization.h" +#include "ColorUtils.h" +#include +#include +#include +#include + +class Bar3DVisualization : public BaseVisualization { +public: + Bar3DVisualization(); + ~Bar3DVisualization(); + + bool initialize(int windowWidth, int windowHeight) override; + void render(const std::vector& fftMagnitudes) override; + bool shouldClose() override; + void cleanup() override; + +private: + GLFWwindow* window; + GLuint vbo, vao; + GLuint shaderProgram; + + // History buffer for z-axis (time) dimension + static const size_t HISTORY_LENGTH = 16; + std::deque> fftHistory; + + // Smoothed FFT for current frame + std::vector smoothedFFT; + + // Camera / projection parameters + float cameraAngleX; + float cameraAngleY; + float cameraDistance; + float autoRotation; + + // Mouse state for interactive rotation + bool mouseDragging; + double lastMouseX, lastMouseY; + + // Perspective projection matrix helper + std::vector perspectiveMatrix(float fov, float aspect, float near, float far); + std::vector lookAtMatrix(float eyeX, float eyeY, float eyeZ, + float centerX, float centerY, float centerZ, + float upX, float upY, float upZ); + std::vector multiplyMat4(const std::vector& a, const std::vector& b); + + // Callbacks + static void framebufferSizeCallback(GLFWwindow* window, int width, int height); + static void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods); + static void cursorPosCallback(GLFWwindow* window, double xpos, double ypos); + static void scrollCallback(GLFWwindow* window, double xoffset, double yoffset); +}; + +#endif diff --git a/src/visualizations/_generation.json b/src/visualizations/_generation.json new file mode 100644 index 0000000..c8ff685 --- /dev/null +++ b/src/visualizations/_generation.json @@ -0,0 +1 @@ +{"agent": "OpenClaw CTO Bot", "pre_task_context": "User: 悦姐,监控脚本发现一个 bounty issue — Convert 2D Bar Graph to 3D Bar Graph\n\nAssistant: 收到老板,分析一下。\n\nIssue #1: devlup-labs/MPV\n- C++ OpenGL 音频可视化项目\n- 需要把 2D 柱状图升级为 3D 透视投影\n- x 轴:频率,y 轴:振幅,z 轴:时间变化\n- 用 Perspective Projection 实现 3D 效果\n\n代码分析:\n- BarVisualization.cpp/h: 现有 2D 实现\n- vertexShader.glsl: 2D shader (vec2 position)\n- BaseVisualization.h: 抽象基类\n- ColorUtils.h: 颜色工具\n- main.cpp: 入口,用 switch 选择可视化类型\n\n实现方案:\n1. 新建 Bar3DVisualization.cpp/h 继承 BaseVisualization\n2. 新建 vertexShader3D.glsl 支持 mat4 MVP\n3. 实现透视投影 + lookAt 相机\n4. 用 deque 存储历史 FFT 数据作为 z 轴\n5. 绘制 3D 方块(6面体)\n6. 支持鼠标拖拽旋转 + 自动旋转\n7. 支持滚轮缩放\n8. 在 main.cpp 添加选项 5", "timestamp": "2026-05-22T15:30:00+08:00"} \ No newline at end of file diff --git a/src/visualizations/vertexShader3D.glsl b/src/visualizations/vertexShader3D.glsl new file mode 100644 index 0000000..49b0edd --- /dev/null +++ b/src/visualizations/vertexShader3D.glsl @@ -0,0 +1,12 @@ +#version 330 core +layout(location = 0) in vec3 aPos; +layout(location = 1) in vec3 aColor; + +out vec3 vertexColor; + +uniform mat4 uMVP; + +void main() { + gl_Position = uMVP * vec4(aPos, 1.0); + vertexColor = aColor; +} From c6b7ec26b7b7aa945e6ade33cab5147b57134ec4 Mon Sep 17 00:00:00 2001 From: TimLiu Date: Fri, 22 May 2026 16:25:15 +0800 Subject: [PATCH 2/2] [OpenClaw CTO Bot] feat: Add Bass/Mid/Treble audio frequency band variables Fixes #2 - Added AudioVariables struct with bass, mid, treble + EMA smoothed versions - Added extractBands() method in AudioProcessor - Frequency ranges: Bass 20-250Hz, Mid 250-4kHz, Treble 4k-20kHz - EMA smoothing (decay=0.1) for bassAtt, midAtt, trebleAtt - Added getAudioVariables() getter - getFFTData() now also extracts band variables --- src/Audio.cpp | 68 ++++++++++++++++++++++++++++++++++++++++++-- src/Audio.h | 19 +++++++++++++ src/_generation.json | 1 + 3 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 src/_generation.json diff --git a/src/Audio.cpp b/src/Audio.cpp index aec08f6..06b402e 100644 --- a/src/Audio.cpp +++ b/src/Audio.cpp @@ -8,8 +8,9 @@ using namespace std; AudioProcessor::AudioProcessor(size_t bufferSize) - : bufferSize(bufferSize), audioReader(nullptr), fftProcessor(nullptr), stream(nullptr), isBufferReady(false) { + : bufferSize(bufferSize), audioReader(nullptr), fftProcessor(nullptr), stream(nullptr), isBufferReady(false), emaDecay(0.1f) { sharedBuffer.resize(bufferSize); + audioVars = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}; } AudioProcessor::~AudioProcessor() { @@ -58,8 +59,71 @@ vector AudioProcessor::getFFTData() { bufferReady.wait(lock, [this] { return isBufferReady; }); fftProcessor->computeFFT(sharedBuffer); + auto magnitudes = fftProcessor->getMagnitudes(); + + // Extract frequency band variables + extractBands(magnitudes); + isBufferReady = false; - return fftProcessor->getMagnitudes(); + return magnitudes; +} + +const AudioVariables& AudioProcessor::getAudioVariables() const { + return audioVars; +} + +void AudioProcessor::extractBands(const vector& magnitudes) { + /* + * Extract Bass, Mid, and Treble loudness from FFT magnitudes. + * Frequency band ranges (approximate, based on typical audio): + * Bass: 20 Hz - 250 Hz → bins 0 to ~sampleRate*250/(bufferSize) + * Mid: 250 Hz - 4000 Hz + * Treble: 4000 Hz - 20000 Hz + * + * For a typical 44100 Hz sample rate with 1024 buffer: + * bin resolution = 44100 / 1024 ≈ 43 Hz per bin + * Bass: bins 0-5 + * Mid: bins 6-93 + * Treble: bins 94-465 + */ + if (magnitudes.empty()) return; + + size_t n = magnitudes.size(); // bufferSize / 2 + float sampleRate = audioReader ? audioReader->getSampleRate() : 44100.0f; + float binResolution = sampleRate / (float)(bufferSize); + + // Define band boundaries in bins + size_t bassEnd = min((size_t)(250.0f / binResolution), n); + size_t midEnd = min((size_t)(4000.0f / binResolution), n); + + // Compute average energy for each band + float bassEnergy = 0.0f; + for (size_t i = 0; i < bassEnd; ++i) { + bassEnergy += magnitudes[i]; + } + bassEnergy = (bassEnd > 0) ? bassEnergy / (float)bassEnd : 0.0f; + + float midEnergy = 0.0f; + for (size_t i = bassEnd; i < midEnd; ++i) { + midEnergy += magnitudes[i]; + } + midEnergy = (midEnd > bassEnd) ? midEnergy / (float)(midEnd - bassEnd) : 0.0f; + + float trebleEnergy = 0.0f; + for (size_t i = midEnd; i < n; ++i) { + trebleEnergy += magnitudes[i]; + } + trebleEnergy = (n > midEnd) ? trebleEnergy / (float)(n - midEnd) : 0.0f; + + // Normalize to 0-1 range (soft clamp) + audioVars.bass = min(bassEnergy / 50.0f, 1.0f); + audioVars.mid = min(midEnergy / 30.0f, 1.0f); + audioVars.treble = min(trebleEnergy / 15.0f, 1.0f); + + // Apply EMA smoothing for attenuated versions + audioVars.bassAtt = audioVars.bassAtt * (1.0f - emaDecay) + audioVars.bass * emaDecay; + audioVars.midAtt = audioVars.midAtt * (1.0f - emaDecay) + audioVars.mid * emaDecay; + audioVars.trebleAtt = audioVars.trebleAtt * (1.0f - emaDecay) + audioVars.treble * emaDecay; } int AudioProcessor::audioCallback(const void* inputBuffer, void* outputBuffer, unsigned long framesPerBuffer, diff --git a/src/Audio.h b/src/Audio.h index a5f365e..7b562e6 100644 --- a/src/Audio.h +++ b/src/Audio.h @@ -9,6 +9,16 @@ using namespace std; +// Audio frequency band variables +struct AudioVariables { + float bass; + float mid; + float treble; + float bassAtt; // Smoothed (EMA) bass + float midAtt; // Smoothed (EMA) mid + float trebleAtt; // Smoothed (EMA) treble +}; + class AudioProcessor { public: AudioProcessor(size_t bufferSize); @@ -18,6 +28,9 @@ class AudioProcessor { vector getFFTData(); + // Get extracted audio frequency band variables + const AudioVariables& getAudioVariables() const; + bool startProcessing(); void cleanup(); @@ -32,8 +45,14 @@ class AudioProcessor { condition_variable bufferReady; bool isBufferReady; + AudioVariables audioVars; + float emaDecay; // EMA smoothing factor + void* stream; + // Extract Bass/Mid/Treble from FFT magnitudes + void extractBands(const vector& magnitudes); + static int audioCallback(const void* inputBuffer, void* outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void* userData); }; diff --git a/src/_generation.json b/src/_generation.json new file mode 100644 index 0000000..6462c7a --- /dev/null +++ b/src/_generation.json @@ -0,0 +1 @@ +{"agent": "OpenClaw CTO Bot", "pre_task_context": "User: 悦姐,修复 devlup-labs/MPV #2 — 添加 Bass/Mid/Treble 频率分离变量\n\nAssistant: 分析中...\n\nIssue #2: Add Audio Variables\n- 目前 FFT 处理只返回原始 magnitude 数据\n- 需要提取 Bass/Mid/Treble 频率带\n- 需要 EMA 平滑版本 (bassAtt, midAtt, trebleAtt)\n\n代码分析:\n- FFTProcessor.cpp/h: computeFFT + getMagnitudes\n- AudioProcessor.cpp/h: getFFTData 返回 magnitude\n- 采样率从 AudioReader 获取\n\n实现方案:\n1. Audio.h: 添加 AudioVariables struct (bass, mid, treble, bassAtt, midAtt, trebleAtt)\n2. Audio.cpp: 添加 extractBands() 方法\n3. 频率范围: Bass 20-250Hz, Mid 250-4000Hz, Treble 4000-20000Hz\n4. EMA 衰减因子 0.1\n5. getFFTData() 里调用 extractBands\n6. 新增 getAudioVariables() getter", "timestamp": "2026-05-22T16:25:00+08:00"} \ No newline at end of file