Add Reverbation DSP effect and fix FFTFilter include paths

This commit is contained in:
2026-03-21 12:16:19 +08:00
parent 2cc9d58edd
commit 00c2699542
4 changed files with 202 additions and 3 deletions

View File

@@ -246,6 +246,8 @@ add_library(XCEngine STATIC
# Audio DSP Effects
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Audio/FFTFilter.h
${CMAKE_CURRENT_SOURCE_DIR}/src/Audio/FFTFilter.cpp
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Audio/Reverbation.h
${CMAKE_CURRENT_SOURCE_DIR}/src/Audio/Reverbation.cpp
# Third-party (KissFFT)
${CMAKE_CURRENT_SOURCE_DIR}/third_party/kissfft/kiss_fft.h

View File

@@ -0,0 +1,74 @@
#pragma once
#include "IAudioEffect.h"
#include <vector>
namespace XCEngine {
namespace Audio {
class Reverbation : public IAudioEffect {
public:
Reverbation();
~Reverbation() override;
void ProcessAudio(float* buffer, uint32 sampleCount, uint32 channels) override;
void SetRoomSize(float size);
float GetRoomSize() const { return m_roomSize; }
void SetDamping(float damping);
float GetDamping() const { return m_damping; }
void SetWetMix(float wetMix) override;
float GetWetMix() const override { return m_wetMix; }
void SetDryMix(float dryMix);
float GetDryMix() const { return m_dryMix; }
void SetWidth(float width);
float GetWidth() const { return m_width; }
void SetFreeze(bool freeze);
bool IsFreeze() const { return m_freeze; }
private:
void ProcessCombFilter(float* buffer, uint32 sampleCount, uint32 channel, uint32 combIndex);
void ProcessAllPassFilter(float* buffer, uint32 sampleCount, uint32 channel);
private:
float m_roomSize = 0.5f;
float m_damping = 0.5f;
float m_wetMix = 0.3f;
float m_dryMix = 0.7f;
float m_width = 1.0f;
bool m_freeze = false;
static constexpr uint32 CombCount = 8;
static constexpr uint32 AllPassCount = 4;
static constexpr uint32 MaxDelayLength = 2000;
struct CombFilter {
std::vector<float> buffer;
uint32 bufferSize = 0;
uint32 writeIndex = 0;
float feedback = 0.0f;
float damp1 = 0.0f;
float damp2 = 0.0f;
float filterStore = 0.0f;
};
struct AllPassFilter {
std::vector<float> buffer;
uint32 bufferSize = 0;
uint32 writeIndex = 0;
float feedback = 0.0f;
};
CombFilter m_combFilters[CombCount];
AllPassFilter m_allPassFilters[AllPassCount];
uint32 m_sampleRate = 48000;
};
} // namespace Audio
} // namespace XCEngine

View File

@@ -1,8 +1,8 @@
#include "FFTFilter.h"
#include <XCEngine/Audio/FFTFilter.h>
#include <cmath>
#include <cstring>
#include "../../third_party/kissfft/kiss_fftr.h"
#include "../../third_party/kissfft/kiss_fft.h"
#include <kissfft/kiss_fftr.h>
#include <kissfft/kiss_fft.h>
namespace XCEngine {
namespace Audio {

View File

@@ -0,0 +1,123 @@
#include <XCEngine/Audio/Reverbation.h>
#include <algorithm>
#include <cstring>
namespace XCEngine {
namespace Audio {
static const uint32 CombTuning[] = { 1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617 };
static const uint32 AllPassTuning[] = { 556, 441, 341, 225 };
Reverbation::Reverbation()
: m_sampleRate(48000)
{
for (uint32 i = 0; i < CombCount; ++i) {
m_combFilters[i].bufferSize = CombTuning[i];
m_combFilters[i].buffer.resize(m_combFilters[i].bufferSize, 0.0f);
m_combFilters[i].writeIndex = 0;
m_combFilters[i].feedback = 0.0f;
m_combFilters[i].damp1 = 0.0f;
m_combFilters[i].damp2 = 0.0f;
m_combFilters[i].filterStore = 0.0f;
}
for (uint32 i = 0; i < AllPassCount; ++i) {
m_allPassFilters[i].bufferSize = AllPassTuning[i];
m_allPassFilters[i].buffer.resize(m_allPassFilters[i].bufferSize, 0.0f);
m_allPassFilters[i].writeIndex = 0;
m_allPassFilters[i].feedback = 0.5f;
}
SetRoomSize(0.5f);
SetDamping(0.5f);
}
Reverbation::~Reverbation() {
}
void Reverbation::ProcessAudio(float* buffer, uint32 sampleCount, uint32 channels) {
if (!m_enabled || buffer == nullptr || sampleCount == 0) {
return;
}
if (channels == 0) {
return;
}
for (uint32 i = 0; i < sampleCount; ++i) {
float input = buffer[i * channels];
float wet = 0.0f;
for (uint32 c = 0; c < CombCount; ++c) {
float output = m_combFilters[c].buffer[m_combFilters[c].writeIndex];
m_combFilters[c].filterStore = output * m_combFilters[c].damp2 + m_combFilters[c].filterStore * m_combFilters[c].damp1;
m_combFilters[c].buffer[m_combFilters[c].writeIndex] = input + m_combFilters[c].filterStore * m_combFilters[c].feedback;
m_combFilters[c].writeIndex++;
if (m_combFilters[c].writeIndex >= m_combFilters[c].bufferSize) {
m_combFilters[c].writeIndex = 0;
}
wet += output;
}
for (uint32 a = 0; a < AllPassCount; ++a) {
float output = m_allPassFilters[a].buffer[m_allPassFilters[a].writeIndex];
float temp = output;
m_allPassFilters[a].buffer[m_allPassFilters[a].writeIndex] = wet + output * m_allPassFilters[a].feedback;
wet = -wet + output;
wet += temp;
m_allPassFilters[a].writeIndex++;
if (m_allPassFilters[a].writeIndex >= m_allPassFilters[a].bufferSize) {
m_allPassFilters[a].writeIndex = 0;
}
}
float outSample = input * m_dryMix + wet * m_wetMix;
for (uint32 ch = 0; ch < channels; ++ch) {
buffer[i * channels + ch] = outSample;
}
}
}
void Reverbation::SetRoomSize(float size) {
m_roomSize = std::max(0.0f, std::min(1.0f, size));
float roomScale = 0.28f + 0.7f * m_roomSize;
for (uint32 i = 0; i < CombCount; ++i) {
m_combFilters[i].feedback = roomScale;
}
}
void Reverbation::SetDamping(float damping) {
m_damping = std::max(0.0f, std::min(1.0f, damping));
for (uint32 i = 0; i < CombCount; ++i) {
m_combFilters[i].damp1 = m_damping * 0.4f;
m_combFilters[i].damp2 = 1.0f - m_damping * 0.4f;
}
}
void Reverbation::SetWetMix(float wetMix) {
m_wetMix = std::max(0.0f, std::min(1.0f, wetMix));
}
void Reverbation::SetDryMix(float dryMix) {
m_dryMix = std::max(0.0f, std::min(1.0f, dryMix));
}
void Reverbation::SetWidth(float width) {
m_width = std::max(0.0f, std::min(1.0f, width));
}
void Reverbation::SetFreeze(bool freeze) {
m_freeze = freeze;
}
} // namespace Audio
} // namespace XCEngine