Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 55 additions & 2 deletions audio/FFTProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@

using namespace std;

FFTProcessor::FFTProcessor(size_t bufferSize)
: bufferSize(bufferSize), magnitudes(bufferSize / 2, 0.0f) {
FFTProcessor::FFTProcessor(size_t bufferSize, int sampleRate)
: bufferSize(bufferSize), sampleRate(sampleRate), magnitudes(bufferSize / 2, 0.0f),
bass(0.0f), mid(0.0f), treble(0.0f), bassAtt(0.0f), midAtt(0.0f), trebleAtt(0.0f) {
// Allocate FFT input/output arrays
fftInput = new float[bufferSize];
fftOutput = new float[bufferSize];
Expand Down Expand Up @@ -45,8 +46,60 @@ void FFTProcessor::computeFFT(const vector<float>& audioData) {
float imag = (i == 0 || i == bufferSize / 2) ? 0 : fftOutput[bufferSize - i];
magnitudes[i] = sqrt(real * real + imag * imag);
}

// Compute Audio Variables (Bass, Mid, Treble)
float newBass = 0.0f;
float newMid = 0.0f;
float newTreble = 0.0f;

int bassCount = 0;
int midCount = 0;
int trebleCount = 0;

for (size_t i = 1; i < bufferSize / 2; ++i) {
float freq = static_cast<float>(i * sampleRate) / bufferSize;
float mag = magnitudes[i];

if (freq >= 20.0f && freq < 250.0f) {
newBass += mag;
bassCount++;
} else if (freq >= 250.0f && freq < 2000.0f) {
newMid += mag;
midCount++;
} else if (freq >= 2000.0f && freq < 20000.0f) {
newTreble += mag;
trebleCount++;
}
}

if (bassCount > 0) newBass /= bassCount;
if (midCount > 0) newMid /= midCount;
if (trebleCount > 0) newTreble /= trebleCount;

bass = newBass;
mid = newMid;
treble = newTreble;

// EMA for attenuated values (alpha = 0.2)
float alpha = 0.2f;
if (bassAtt == 0.0f && midAtt == 0.0f && trebleAtt == 0.0f) {
bassAtt = bass;
midAtt = mid;
trebleAtt = treble;
} else {
bassAtt = alpha * bass + (1.0f - alpha) * bassAtt;
midAtt = alpha * mid + (1.0f - alpha) * midAtt;
trebleAtt = alpha * treble + (1.0f - alpha) * trebleAtt;
}
}

const vector<float>& FFTProcessor::getMagnitudes() const {
return magnitudes;
}

float FFTProcessor::getBass() const { return bass; }
float FFTProcessor::getMid() const { return mid; }
float FFTProcessor::getTreble() const { return treble; }
float FFTProcessor::getBassAtt() const { return bassAtt; }
float FFTProcessor::getMidAtt() const { return midAtt; }
float FFTProcessor::getTrebleAtt() const { return trebleAtt; }
13 changes: 12 additions & 1 deletion audio/FFTProcessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,29 @@ using namespace std;

class FFTProcessor {
public:
FFTProcessor(size_t bufferSize);
FFTProcessor(size_t bufferSize, int sampleRate = 44100);
~FFTProcessor();

void computeFFT(const vector<float>& audioData);
const vector<float>& getMagnitudes() const;

float getBass() const;
float getMid() const;
float getTreble() const;
float getBassAtt() const;
float getMidAtt() const;
float getTrebleAtt() const;

private:
size_t bufferSize;
int sampleRate;
vector<float> magnitudes;
float* fftInput;
float* fftOutput;
void* fftPlan; // Plan type depends on FFTW version

float bass, mid, treble;
float bassAtt, midAtt, trebleAtt;
};

#endif // FFTPROCESSOR_H
9 changes: 8 additions & 1 deletion src/Audio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ bool AudioProcessor::loadAudioFile(const string& fileName) {
cerr << "Failed to load audio file: " << fileName << endl;
return false;
}
fftProcessor = new FFTProcessor(bufferSize);
fftProcessor = new FFTProcessor(bufferSize, audioReader->getSampleRate());
return true;
}

Expand Down Expand Up @@ -62,6 +62,13 @@ vector<float> AudioProcessor::getFFTData() {
return fftProcessor->getMagnitudes();
}

float AudioProcessor::getBass() const { return fftProcessor ? fftProcessor->getBass() : 0.0f; }
float AudioProcessor::getMid() const { return fftProcessor ? fftProcessor->getMid() : 0.0f; }
float AudioProcessor::getTreble() const { return fftProcessor ? fftProcessor->getTreble() : 0.0f; }
float AudioProcessor::getBassAtt() const { return fftProcessor ? fftProcessor->getBassAtt() : 0.0f; }
float AudioProcessor::getMidAtt() const { return fftProcessor ? fftProcessor->getMidAtt() : 0.0f; }
float AudioProcessor::getTrebleAtt() const { return fftProcessor ? fftProcessor->getTrebleAtt() : 0.0f; }

int AudioProcessor::audioCallback(const void* inputBuffer, void* outputBuffer, unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void* userData) {
AudioProcessor* processor = static_cast<AudioProcessor*>(userData);
Expand Down
7 changes: 7 additions & 0 deletions src/Audio.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ class AudioProcessor {

vector<float> getFFTData();

float getBass() const;
float getMid() const;
float getTreble() const;
float getBassAtt() const;
float getMidAtt() const;
float getTrebleAtt() const;

bool startProcessing();

void cleanup();
Expand Down
138 changes: 121 additions & 17 deletions src/visualizations/BarVisualization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,98 @@
#include "ColorUtils.h"
#include <cmath>
#include <iostream>
#include <vector>

namespace {

void multiplyMatrix(const float* A, const float* B, float* C) {
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
C[i * 4 + j] = 0;
for (int k = 0; k < 4; k++) {
C[i * 4 + j] += A[i * 4 + k] * B[k * 4 + j];
}
}
}
}

void getPerspectiveMatrix(float fov, float aspect, float nearZ, float farZ, float* result) {
float f = 1.0f / tan(fov / 2.0f);
for(int i = 0; i < 16; i++) result[i] = 0.0f;
result[0 * 4 + 0] = f / aspect;
result[1 * 4 + 1] = f;
result[2 * 4 + 2] = (farZ + nearZ) / (nearZ - farZ);
result[2 * 4 + 3] = (2.0f * farZ * nearZ) / (nearZ - farZ);
result[3 * 4 + 2] = -1.0f;
}

void getRotationMatrixX(float angle, float* result) {
for(int i = 0; i < 16; i++) result[i] = 0.0f;
float c = cos(angle);
float s = sin(angle);
result[0 * 4 + 0] = 1.0f;
result[1 * 4 + 1] = c;
result[1 * 4 + 2] = -s;
result[2 * 4 + 1] = s;
result[2 * 4 + 2] = c;
result[3 * 4 + 3] = 1.0f;
}

void getRotationMatrixY(float angle, float* result) {
for(int i = 0; i < 16; i++) result[i] = 0.0f;
float c = cos(angle);
float s = sin(angle);
result[0 * 4 + 0] = c;
result[0 * 4 + 2] = s;
result[1 * 4 + 1] = 1.0f;
result[2 * 4 + 0] = -s;
result[2 * 4 + 2] = c;
result[3 * 4 + 3] = 1.0f;
}

void getTranslationMatrix(float tx, float ty, float tz, float* result) {
for(int i = 0; i < 16; i++) result[i] = 0.0f;
result[0 * 4 + 0] = 1.0f;
result[1 * 4 + 1] = 1.0f;
result[2 * 4 + 2] = 1.0f;
result[3 * 4 + 3] = 1.0f;
result[0 * 4 + 3] = tx;
result[1 * 4 + 3] = ty;
result[2 * 4 + 3] = tz;
}

void addCube(std::vector<float>& vertices, float x, float y, float z, float w, float h, float d, Color c) {
float v[8][3] = {
{x, y, z}, {x+w, y, z}, {x+w, y+h, z}, {x, y+h, z},
{x, y, z-d}, {x+w, y, z-d}, {x+w, y+h, z-d}, {x, y+h, z-d}
};
int indices[36] = {
0, 1, 2, 2, 3, 0, // front
1, 5, 6, 6, 2, 1, // right
5, 4, 7, 7, 6, 5, // back
4, 0, 3, 3, 7, 4, // left
3, 2, 6, 6, 7, 3, // top
4, 5, 1, 1, 0, 4 // bottom
};
for (int i = 0; i < 6; i++) {
float faceShade = 1.0f;
if (i == 1 || i == 3) faceShade = 0.7f;
else if (i == 4 || i == 5) faceShade = 0.5f;
else if (i == 2) faceShade = 0.8f;

for (int j = 0; j < 6; j++) {
int idx = indices[i * 6 + j];
vertices.push_back(v[idx][0]);
vertices.push_back(v[idx][1]);
vertices.push_back(v[idx][2]);
vertices.push_back(c.r * faceShade);
vertices.push_back(c.g * faceShade);
vertices.push_back(c.b * faceShade);
}
}
}

}
BarVisualization::BarVisualization() : window(nullptr), vbo(0), vao(0), smoothedFFT(128, 0.0f) {}

BarVisualization::~BarVisualization() {
Expand Down Expand Up @@ -43,16 +134,18 @@ bool BarVisualization::initialize(int windowWidth, int windowHeight) {
glBindBuffer(GL_ARRAY_BUFFER, vbo);

// ✅ Define vertex attribute layout (Position: location 0)
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

// ✅ Define vertex attribute layout (Color: location 1)
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(2 * sizeof(float)));
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);

glEnable(GL_DEPTH_TEST);

glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

shaderProgram = createShaderProgram("visualizations/vertexShader.glsl", "visualizations/fragmentShader.glsl");
shaderProgram = createShaderProgram("visualizations/vertexShader3D.glsl", "visualizations/fragmentShader.glsl");
if (shaderProgram == 0) {
std::cerr << "ERROR: Failed to create shader program!" << std::endl;
exit(1);
Expand All @@ -64,9 +157,28 @@ bool BarVisualization::initialize(int windowWidth, int windowHeight) {
}

void BarVisualization::render(const std::vector<float>& fftMagnitudes) {
glClear(GL_COLOR_BUFFER_BIT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUseProgram(shaderProgram); // ✅ Ensure shaders are active before drawing

int width, height;
glfwGetWindowSize(window, &width, &height);
float aspect = (float)width / (height == 0 ? 1 : height);

float proj[16], view[16], rotX[16], rotY[16], modelMatrix[16], viewProj[16], mvp[16];
getPerspectiveMatrix(45.0f * (M_PI / 180.0f), aspect, 0.1f, 100.0f, proj);
getTranslationMatrix(0.0f, -0.5f, -3.0f, view);

// Rotate to look down slightly and from an angle
getRotationMatrixX(20.0f * (M_PI / 180.0f), rotX);
getRotationMatrixY(30.0f * (M_PI / 180.0f), rotY);
multiplyMatrix(rotX, rotY, modelMatrix);

multiplyMatrix(proj, view, viewProj);
multiplyMatrix(viewProj, modelMatrix, mvp);

GLint mvpLoc = glGetUniformLocation(shaderProgram, "MVP");
glUniformMatrix4fv(mvpLoc, 1, GL_TRUE, mvp);

if (fftMagnitudes.empty()) {
std::cerr << "ERROR: No FFT data received!\n";
return;
Expand All @@ -75,9 +187,9 @@ void BarVisualization::render(const std::vector<float>& fftMagnitudes) {
size_t numBars = fftMagnitudes.size() / 8; // Reduce number of bars for clarity
std::vector<float> vertices;

float barWidth = 2.0f / numBars; // Normalize to OpenGL's -1 to 1 range
float maxHeight = 0.9f; // Ensure bars stay within screen height
float minHeight = 0.02f; // Minimum bar height to keep them visible
float barWidth = 4.0f / numBars; // Wider range for 3D
float maxHeight = 3.0f; // Taller bars in 3D space
float minHeight = 0.05f; // Minimum bar height to keep them visible

// Ensure smoothedFFT is the correct size
if (smoothedFFT.size() != numBars) {
Expand Down Expand Up @@ -113,18 +225,10 @@ void BarVisualization::render(const std::vector<float>& fftMagnitudes) {
float height = smoothedFFT[i] * maxHeight;
if (height < minHeight) height = minHeight; // Prevents bars from disappearing

float x = -1.0f + i * barWidth;
float x = -2.0f + i * barWidth;
Color color = getColorFromMagnitude(smoothedFFT[i], 0.0f, 1.0f); // ✅ Dynamic color

vertices.insert(vertices.end(), {
x, -1.0f, color.r, color.g, color.b,
x + barWidth * 0.8f, -1.0f, color.r, color.g, color.b,
x, height - 1.0f, color.r, color.g, color.b,

x + barWidth * 0.8f, -1.0f, color.r, color.g, color.b,
x + barWidth * 0.8f, height - 1.0f, color.r, color.g, color.b,
x, height - 1.0f, color.r, color.g, color.b
});
addCube(vertices, x, 0.0f, 0.0f, barWidth * 0.8f, height, 0.4f, color);
}


Expand Down
11 changes: 11 additions & 0 deletions src/visualizations/vertexShader3D.glsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aColor;

out vec3 vertexColor;
uniform mat4 MVP;

void main() {
gl_Position = MVP * vec4(aPos, 1.0);
vertexColor = aColor;
}