Add IAudioEffect interface and FFTFilter DSP effect using KissFFT

This commit is contained in:
2026-03-21 12:08:16 +08:00
parent dfc948fc89
commit b68cde82b2
4 changed files with 192 additions and 0 deletions

View File

@@ -226,6 +226,7 @@ add_library(XCEngine STATIC
# Audio
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Audio/AudioTypes.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Audio/AudioConfig.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Audio/IAudioEffect.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Audio/IAudioBackend.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Audio/AudioSystem.h
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Audio/WASAPI/WASAPIBackend.h
@@ -242,6 +243,10 @@ add_library(XCEngine STATIC
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Audio/AudioMixer.h
${CMAKE_CURRENT_SOURCE_DIR}/src/Audio/AudioMixer.cpp
# Audio DSP Effects
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Audio/FFTFilter.h
${CMAKE_CURRENT_SOURCE_DIR}/src/Audio/FFTFilter.cpp
# Third-party (KissFFT)
${CMAKE_CURRENT_SOURCE_DIR}/third_party/kissfft/kiss_fft.h
${CMAKE_CURRENT_SOURCE_DIR}/third_party/kissfft/kiss_fft.c

View File

@@ -0,0 +1,42 @@
#pragma once
#include "IAudioEffect.h"
#include <vector>
namespace XCEngine {
namespace Audio {
class FFTFilter : public IAudioEffect {
public:
FFTFilter();
explicit FFTFilter(uint32 fftSize);
~FFTFilter() override;
void ProcessAudio(float* buffer, uint32 sampleCount, uint32 channels) override;
void SetFFTSize(uint32 size);
uint32 GetFFTSize() const { return m_fftSize; }
void SetSmoothingFactor(float factor);
float GetSmoothingFactor() const { return m_smoothingFactor; }
const float* GetSpectrumData() const { return m_spectrumData.data(); }
size_t GetSpectrumSize() const { return m_spectrumData.size(); }
private:
void InitializeFFT(uint32 size);
void ComputeFFT(const float* input, uint32 size);
private:
uint32 m_fftSize = 1024;
float m_smoothingFactor = 0.8f;
std::vector<float> m_spectrumData;
std::vector<float> m_prevSpectrum;
void* m_fftConfig = nullptr;
void* m_fftrConfig = nullptr;
};
} // namespace Audio
} // namespace XCEngine

View File

@@ -0,0 +1,26 @@
#pragma once
#include "AudioTypes.h"
namespace XCEngine {
namespace Audio {
class IAudioEffect {
public:
virtual ~IAudioEffect() = default;
virtual void ProcessAudio(float* buffer, uint32 sampleCount, uint32 channels) = 0;
virtual void SetEnabled(bool enabled) { m_enabled = enabled; }
virtual bool IsEnabled() const { return m_enabled; }
virtual void SetWetMix(float wetMix) { m_wetMix = wetMix; }
virtual float GetWetMix() const { return m_wetMix; }
protected:
bool m_enabled = true;
float m_wetMix = 1.0f;
};
} // namespace Audio
} // namespace XCEngine

View File

@@ -0,0 +1,119 @@
#include "FFTFilter.h"
#include <cmath>
#include <cstring>
#include "../../third_party/kissfft/kiss_fftr.h"
#include "../../third_party/kissfft/kiss_fft.h"
namespace XCEngine {
namespace Audio {
FFTFilter::FFTFilter()
: m_spectrumData(512, 0.0f)
, m_prevSpectrum(512, 0.0f)
{
InitializeFFT(m_fftSize);
}
FFTFilter::FFTFilter(uint32 fftSize)
: m_fftSize(fftSize)
, m_spectrumData(fftSize / 2, 0.0f)
, m_prevSpectrum(fftSize / 2, 0.0f)
{
InitializeFFT(fftSize);
}
FFTFilter::~FFTFilter() {
if (m_fftConfig) {
kiss_fft_free(static_cast<kiss_fft_cfg>(m_fftConfig));
m_fftConfig = nullptr;
}
if (m_fftrConfig) {
kiss_fftr_free(static_cast<kiss_fftr_cfg>(m_fftrConfig));
m_fftrConfig = nullptr;
}
}
void FFTFilter::ProcessAudio(float* buffer, uint32 sampleCount, uint32 channels) {
if (!m_enabled || buffer == nullptr || sampleCount == 0) {
return;
}
if (channels == 0) {
return;
}
if (sampleCount < m_fftSize) {
return;
}
uint32 binCount = m_fftSize / 2;
if (m_spectrumData.size() != binCount) {
m_spectrumData.resize(binCount);
m_prevSpectrum.resize(binCount);
}
std::vector<float> monoBuffer(m_fftSize, 0.0f);
for (uint32 i = 0; i < m_fftSize; ++i) {
uint32 channelIndex = (channels == 1) ? 0 : (i % channels);
monoBuffer[i] = buffer[i * channels + channelIndex];
}
ComputeFFT(monoBuffer.data(), m_fftSize);
}
void FFTFilter::SetFFTSize(uint32 size) {
if (size == m_fftSize) {
return;
}
m_fftSize = size;
m_spectrumData.resize(size / 2);
m_prevSpectrum.resize(size / 2);
InitializeFFT(size);
}
void FFTFilter::SetSmoothingFactor(float factor) {
m_smoothingFactor = std::max(0.0f, std::min(1.0f, factor));
}
void FFTFilter::InitializeFFT(uint32 size) {
if (m_fftConfig) {
kiss_fft_free(static_cast<kiss_fft_cfg>(m_fftConfig));
}
if (m_fftrConfig) {
kiss_fftr_free(static_cast<kiss_fftr_cfg>(m_fftrConfig));
}
m_fftConfig = kiss_fft_alloc(size, 0, nullptr, nullptr);
m_fftrConfig = kiss_fftr_alloc(size, 0, nullptr, nullptr);
}
void FFTFilter::ComputeFFT(const float* input, uint32 size) {
if (!m_fftrConfig || !input) {
return;
}
kiss_fft_scalar* timedata = new kiss_fft_scalar[size];
kiss_fft_cpx* freqdata = new kiss_fft_cpx[size / 2 + 1];
for (uint32 i = 0; i < size; ++i) {
timedata[i] = input[i];
}
kiss_fftr(static_cast<kiss_fftr_cfg>(m_fftrConfig), timedata, freqdata);
uint32 binCount = size / 2;
for (uint32 i = 0; i < binCount; ++i) {
float magnitude = std::sqrt(freqdata[i].r * freqdata[i].r + freqdata[i].i * freqdata[i].i);
float dbValue = 20.0f * std::log10(magnitude + 1e-10f);
dbValue = std::max(0.0f, std::min(1.0f, (dbValue + 80.0f) / 80.0f));
m_spectrumData[i] = m_spectrumData[i] * m_smoothingFactor + dbValue * (1.0f - m_smoothingFactor);
}
delete[] timedata;
delete[] freqdata;
}
} // namespace Audio
} // namespace XCEngine