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
48 changes: 46 additions & 2 deletions audio/FFTProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@
#include <fftw3.h>
#include <cmath>
#include <iostream>
#include <numeric>

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),
smoothingFactor(0.2f) { // ProjectM style EMA smoothing factor
// Allocate FFT input/output arrays
fftInput = new float[bufferSize];
fftOutput = new float[bufferSize];
Expand Down Expand Up @@ -45,6 +49,46 @@ 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);
}

// Calculate bass, mid, treble and their attenuated versions
calculateBands();
}

void FFTProcessor::calculateBands() {
// ProjectM style frequency bands (approximate)
// Bass: 20Hz - 250Hz
// Mid: 250Hz - 2000Hz
// Treble: 2000Hz - 20000Hz

float hzPerBin = static_cast<float>(sampleRate) / bufferSize;

size_t bassStartBin = max((size_t)1, static_cast<size_t>(20.0f / hzPerBin));
size_t bassEndBin = min(bufferSize / 2, static_cast<size_t>(250.0f / hzPerBin));

size_t midStartBin = bassEndBin;
size_t midEndBin = min(bufferSize / 2, static_cast<size_t>(2000.0f / hzPerBin));

size_t trebleStartBin = midEndBin;
size_t trebleEndBin = min(bufferSize / 2, static_cast<size_t>(20000.0f / hzPerBin));

auto calculateEnergy = [&](size_t start, size_t end) -> float {
if (start >= end) return 0.0f;
float energy = 0.0f;
for (size_t i = start; i < end; ++i) {
energy += magnitudes[i];
}
return energy / (end - start); // Average energy per bin in this band
};

// Calculate raw values
bass = calculateEnergy(bassStartBin, bassEndBin);
mid = calculateEnergy(midStartBin, midEndBin);
treble = calculateEnergy(trebleStartBin, trebleEndBin);

// Calculate smoothed/attenuated values using EMA (Exponential Moving Average)
bassAtt = (bass * smoothingFactor) + (bassAtt * (1.0f - smoothingFactor));
midAtt = (mid * smoothingFactor) + (midAtt * (1.0f - smoothingFactor));
trebleAtt = (treble * smoothingFactor) + (trebleAtt * (1.0f - smoothingFactor));
}

const vector<float>& FFTProcessor::getMagnitudes() const {
Expand Down
26 changes: 25 additions & 1 deletion audio/FFTProcessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,42 @@ 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 { return bass; }
float getMid() const { return mid; }
float getTreble() const { return treble; }

float getBassAtt() const { return bassAtt; }
float getMidAtt() const { return midAtt; }
float getTrebleAtt() const { return trebleAtt; }

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

// Raw band values
float bass;
float mid;
float treble;

// Attenuated/smoothed band values using EMA
float bassAtt;
float midAtt;
float trebleAtt;

// EMA smoothing factor (0.0 to 1.0)
float smoothingFactor;

void calculateBands();
};

#endif // FFTPROCESSOR_H
10 changes: 9 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,14 @@ 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
9 changes: 9 additions & 0 deletions src/Audio.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ class AudioProcessor {

void cleanup();

// Frequency bands
float getBass() const;
float getMid() const;
float getTreble() const;

float getBassAtt() const;
float getMidAtt() const;
float getTrebleAtt() const;

private:
size_t bufferSize;
class AudioFileReader* audioReader;
Expand Down