From b68cde82b2b04a73b47f133318925b1ab9a587ab Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Sat, 21 Mar 2026 12:08:16 +0800 Subject: [PATCH] Add IAudioEffect interface and FFTFilter DSP effect using KissFFT --- engine/CMakeLists.txt | 5 + engine/include/XCEngine/Audio/FFTFilter.h | 42 +++++++ engine/include/XCEngine/Audio/IAudioEffect.h | 26 ++++ engine/src/Audio/FFTFilter.cpp | 119 +++++++++++++++++++ 4 files changed, 192 insertions(+) create mode 100644 engine/include/XCEngine/Audio/FFTFilter.h create mode 100644 engine/include/XCEngine/Audio/IAudioEffect.h create mode 100644 engine/src/Audio/FFTFilter.cpp diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 04478880..4f1dce5f 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -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 diff --git a/engine/include/XCEngine/Audio/FFTFilter.h b/engine/include/XCEngine/Audio/FFTFilter.h new file mode 100644 index 00000000..fcf4f9e4 --- /dev/null +++ b/engine/include/XCEngine/Audio/FFTFilter.h @@ -0,0 +1,42 @@ +#pragma once + +#include "IAudioEffect.h" +#include + +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 m_spectrumData; + std::vector m_prevSpectrum; + + void* m_fftConfig = nullptr; + void* m_fftrConfig = nullptr; +}; + +} // namespace Audio +} // namespace XCEngine diff --git a/engine/include/XCEngine/Audio/IAudioEffect.h b/engine/include/XCEngine/Audio/IAudioEffect.h new file mode 100644 index 00000000..ed48b83e --- /dev/null +++ b/engine/include/XCEngine/Audio/IAudioEffect.h @@ -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 diff --git a/engine/src/Audio/FFTFilter.cpp b/engine/src/Audio/FFTFilter.cpp new file mode 100644 index 00000000..7f38a3ee --- /dev/null +++ b/engine/src/Audio/FFTFilter.cpp @@ -0,0 +1,119 @@ +#include "FFTFilter.h" +#include +#include +#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(m_fftConfig)); + m_fftConfig = nullptr; + } + if (m_fftrConfig) { + kiss_fftr_free(static_cast(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 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(m_fftConfig)); + } + if (m_fftrConfig) { + kiss_fftr_free(static_cast(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(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