From 3d3869e30d3cfd3c672db970c002e5e7f0351fd0 Mon Sep 17 00:00:00 2001 From: Jack Andersen Date: Thu, 12 May 2016 15:46:41 -1000 Subject: [PATCH] Implement reverb effects --- CMakeLists.txt | 6 +- include/amuse/EffectReverb.hpp | 114 ++++++++ include/amuse/EffectReverbHi.hpp | 32 --- include/amuse/EffectReverbStd.hpp | 31 --- include/amuse/amuse.hpp | 3 +- include/amuse/dsp.h | 11 +- lib/EffectReverb.cpp | 414 ++++++++++++++++++++++++++++++ lib/EffectReverbHi.cpp | 0 lib/EffectReverbStd.cpp | 0 lib/Voice.cpp | 15 ++ lib/dsp.c | 93 ++++--- 11 files changed, 615 insertions(+), 104 deletions(-) create mode 100644 include/amuse/EffectReverb.hpp delete mode 100644 include/amuse/EffectReverbHi.hpp delete mode 100644 include/amuse/EffectReverbStd.hpp create mode 100644 lib/EffectReverb.cpp delete mode 100644 lib/EffectReverbHi.cpp delete mode 100644 lib/EffectReverbStd.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 56df182..6b463d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,8 +13,7 @@ set(SOURCES lib/Voice.cpp lib/Submix.cpp lib/EffectBase.cpp - lib/EffectReverbHi.cpp - lib/EffectReverbStd.cpp + lib/EffectReverb.cpp lib/EffectChorus.cpp lib/EffectDelay.cpp lib/SurroundProfiles.cpp @@ -39,8 +38,7 @@ set(HEADERS include/amuse/IBackendVoice.hpp include/amuse/IBackendVoiceAllocator.hpp include/amuse/EffectBase.hpp - include/amuse/EffectReverbHi.hpp - include/amuse/EffectReverbStd.hpp + include/amuse/EffectReverb.hpp include/amuse/EffectChorus.hpp include/amuse/EffectDelay.hpp include/amuse/SurroundProfiles.hpp diff --git a/include/amuse/EffectReverb.hpp b/include/amuse/EffectReverb.hpp new file mode 100644 index 0000000..0b308f7 --- /dev/null +++ b/include/amuse/EffectReverb.hpp @@ -0,0 +1,114 @@ +#ifndef __AMUSE_EFFECTREVERB_HPP__ +#define __AMUSE_EFFECTREVERB_HPP__ + +#include "EffectBase.hpp" +#include "amuse/Common.hpp" +#include + +namespace amuse +{ + +/** Delay state for one 'tap' of the reverb effect */ +struct ReverbDelayLine +{ + int32_t x0_inPoint = 0; + int32_t x4_outPoint = 0; + int32_t x8_length = 0; + std::unique_ptr xc_inputs; + float x10_lastInput = 0.f; + + void allocate(int32_t delay); + void setdelay(int32_t delay); +}; + +/** Reverb effect with configurable reflection filtering */ +template +class EffectReverb : public EffectBase +{ +protected: + ReverbDelayLine x0_x0_AP[8][AP] = {}; /**< All-pass delay lines */ + ReverbDelayLine x78_xb4_C[8][C] = {}; /**< Comb delay lines */ + float xf0_x168_allPassCoef = 0.f; /**< All-pass mix coefficient */ + float xf4_x16c_combCoef[8][C] = {}; /**< Comb mix coefficients */ + float x10c_x190_lpLastout[8] = {}; /**< Last low-pass results */ + float x118_x19c_level = 0.f; /**< Internal wet/dry mix factor */ + float x11c_x1a0_damping = 0.f; /**< Low-pass damping */ + int32_t x120_x1a4_preDelayTime = 0; /**< Sample count of pre-delay */ + std::unique_ptr x124_x1ac_preDelayLine[8]; /**< Dedicated pre-delay buffers */ + float* x130_x1b8_preDelayPtr[8] = {}; /**< Current pre-delay pointers */ + + float x140_x1c8_coloration; /**< [0.0, 1.0] influences filter coefficients to define surface characteristics of a room */ + float x144_x1cc_mix; /**< [0.0, 1.0] dry/wet mix factor of reverb effect */ + float x148_x1d0_time; /**< [0.01, 10.0] time in seconds for reflection decay */ + float x14c_x1d4_damping; /**< [0.0, 1.0] damping factor influencing low-pass filter of reflections */ + float x150_x1d8_preDelay; /**< [0.0, 0.1] time in seconds before initial reflection heard */ + + double m_sampleRate; /**< copy of sample rate */ + bool m_dirty = true; /**< needs update of internal parameter data */ + void _update(); +public: + EffectReverb(float coloration, float mix, float time, + float damping, float preDelay, double sampleRate); + void applyEffect(T* audio, size_t frameCount, const ChannelMap& chanMap); + + void setColoration(float coloration) + { + x140_x1c8_coloration = clamp(0.f, coloration, 1.f); + m_dirty = true; + } + void setMix(float mix) + { + x144_x1cc_mix = clamp(0.f, mix, 1.f); + m_dirty = true; + } + void setTime(float time) + { + x148_x1d0_time = clamp(0.01f, time, 10.f); + m_dirty = true; + } + void setDamping(float damping) + { + x14c_x1d4_damping = clamp(0.f, damping, 1.f); + m_dirty = true; + } + void setPreDelay(float preDelay) + { + x150_x1d8_preDelay = clamp(0.f, preDelay, 0.1f); + m_dirty = true; + } +}; + +/** Standard-quality 2-tap reverb */ +template +class EffectReverbStd : public EffectReverb +{ +public: + EffectReverbStd(float coloration, float mix, float time, + float damping, float preDelay, double sampleRate); +}; + +/** High-quality 3-tap reverb */ +template +class EffectReverbHi : public EffectReverb +{ + ReverbDelayLine x78_LP[8] = {}; /**< Per-channel low-pass delay-lines */ + float x1a8_internalCrosstalk; + float x1dc_crosstalk; /**< [0.0, 1.0] factor defining how much reflections are allowed to bleed to other channels */ + void _update(); + void _handleReverb(T* audio, int chanIdx, int chanCount, int sampleCount); + void _doCrosstalk(T* audio, float wet, float dry, int chanCount, int sampleCount); +public: + EffectReverbHi(float coloration, float mix, float time, + float damping, float preDelay, float crosstalk, double sampleRate); + void applyEffect(T* audio, size_t frameCount, const ChannelMap& chanMap); + + void setCrosstalk(float crosstalk) + { + x1dc_crosstalk = clamp(0.f, crosstalk, 1.f); + EffectReverb::m_dirty = true; + } +}; + +} + +#endif // __AMUSE_EFFECTREVERB_HPP__ diff --git a/include/amuse/EffectReverbHi.hpp b/include/amuse/EffectReverbHi.hpp deleted file mode 100644 index 78dd0eb..0000000 --- a/include/amuse/EffectReverbHi.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef __AMUSE_EFFECTREVERBHI_HPP__ -#define __AMUSE_EFFECTREVERBHI_HPP__ - -#include "EffectBase.hpp" - -namespace amuse -{ - -/** Reverb effect with configurable reflection filtering and channel-crosstalk */ -template -class EffectReverbHi : public EffectBase -{ - float m_coloration; /**< [0.0, 1.0] influences filter coefficients to define surface characteristics of a room */ - float m_mix; /**< [0.0, 1.0] dry/wet mix factor of reverb effect */ - float m_time; /**< [0.01, 10.0] time in seconds for reflection decay */ - float m_damping; /**< [0.0, 1.0] damping factor influencing low-pass filter of reflections */ - float m_preDelay; /**< [0.0, 0.1] time in seconds before initial reflection heard */ - float m_crosstalk; /**< [0.0, 100.0] factor defining how much reflections are allowed to bleed to other channels */ -public: - EffectReverbHi(float coloration, float mix, float time, - float damping, float preDelay, float crosstalk); - void applyEffect(int16_t* audio, size_t frameCount, - const ChannelMap& chanMap, double sampleRate); - void applyEffect(int32_t* audio, size_t frameCount, - const ChannelMap& chanMap, double sampleRate); - void applyEffect(float* audio, size_t frameCount, - const ChannelMap& chanMap, double sampleRate); -}; - -} - -#endif // __AMUSE_EFFECTREVERBHI_HPP__ diff --git a/include/amuse/EffectReverbStd.hpp b/include/amuse/EffectReverbStd.hpp deleted file mode 100644 index 80647d6..0000000 --- a/include/amuse/EffectReverbStd.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef __AMUSE_EFFECTREVERBSTD_HPP__ -#define __AMUSE_EFFECTREVERBSTD_HPP__ - -#include "EffectBase.hpp" - -namespace amuse -{ - -/** Reverb effect with configurable reflection filtering */ -template -class EffectReverbStd : public EffectBase -{ - float m_coloration; /**< [0.0, 1.0] influences filter coefficients to define surface characteristics of a room */ - float m_mix; /**< [0.0, 1.0] dry/wet mix factor of reverb effect */ - float m_time; /**< [0.01, 10.0] time in seconds for reflection decay */ - float m_damping; /**< [0.0, 1.0] damping factor influencing low-pass filter of reflections */ - float m_preDelay; /**< [0.0, 0.1] time in seconds before initial reflection heard */ -public: - EffectReverbStd(float coloration, float mix, float time, - float damping, float preDelay); - void applyEffect(int16_t* audio, size_t frameCount, - const ChannelMap& chanMap, double sampleRate); - void applyEffect(int32_t* audio, size_t frameCount, - const ChannelMap& chanMap, double sampleRate); - void applyEffect(float* audio, size_t frameCount, - const ChannelMap& chanMap, double sampleRate); -}; - -} - -#endif // __AMUSE_EFFECTREVERBSTD_HPP__ diff --git a/include/amuse/amuse.hpp b/include/amuse/amuse.hpp index 51da540..7462db9 100644 --- a/include/amuse/amuse.hpp +++ b/include/amuse/amuse.hpp @@ -8,8 +8,7 @@ #include "AudioGroupSampleDirectory.hpp" #include "EffectChorus.hpp" #include "EffectDelay.hpp" -#include "EffectReverbStd.hpp" -#include "EffectReverbHi.hpp" +#include "EffectReverb.hpp" #include "Emitter.hpp" #include "Engine.hpp" #include "Envelope.hpp" diff --git a/include/amuse/dsp.h b/include/amuse/dsp.h index 96becbe..8f0c03d 100644 --- a/include/amuse/dsp.h +++ b/include/amuse/dsp.h @@ -14,9 +14,6 @@ static inline int16_t DSPSampClamp(int32_t val) return val; } -unsigned DSPDecompressFrameRanged(int16_t* out, const uint8_t* in, - const int16_t coefs[8][2], int16_t* prev1, int16_t* prev2, - unsigned firstSample, unsigned lastSample); unsigned DSPDecompressFrame(int16_t* out, const uint8_t* in, const int16_t coefs[8][2], int16_t* prev1, int16_t* prev2, unsigned lastSample); @@ -27,6 +24,14 @@ unsigned DSPDecompressFrameStereoDupe(int16_t* out, const uint8_t* in, const int16_t coefs[8][2], int16_t* prev1, int16_t* prev2, unsigned lastSample); +unsigned DSPDecompressFrameRanged(int16_t* out, const uint8_t* in, + const int16_t coefs[8][2], int16_t* prev1, int16_t* prev2, + unsigned firstSample, unsigned lastSample); + +unsigned DSPDecompressFrameStateOnly(const uint8_t* in, + const int16_t coefs[8][2], int16_t* prev1, int16_t* prev2, + unsigned lastSample); + #ifdef __cplusplus } #endif diff --git a/lib/EffectReverb.cpp b/lib/EffectReverb.cpp new file mode 100644 index 0000000..282618c --- /dev/null +++ b/lib/EffectReverb.cpp @@ -0,0 +1,414 @@ +#include "amuse/EffectReverb.hpp" +#include "amuse/IBackendVoice.hpp" +#include +#include + +namespace amuse +{ + +/* Comb-filter delays */ +static const size_t CTapDelays[] = +{ + 1789, + 1999, + 2333 +}; + +/* All-pass-filter delays */ +static const size_t APTapDelays[] = +{ + 433, + 149 +}; + +/* Per-channel low-pass delays (Hi-quality reverb only) */ +static const size_t LPTapDelays[] = +{ + 47, + 73, + 67, + 57, + 43, + 57, + 83, + 73 +}; + +void ReverbDelayLine::allocate(int32_t delay) +{ + delay += 2; + x8_length = delay * sizeof(float); + xc_inputs.reset(new float[delay]); + memset(xc_inputs.get(), 0, x8_length); + x10_lastInput = 0.f; + setdelay(delay / 2); + x0_inPoint = 0; + x4_outPoint = 0; +} + +void ReverbDelayLine::setdelay(int32_t delay) +{ + x4_outPoint = x0_inPoint - delay * sizeof(float); + while (x4_outPoint < 0) + x4_outPoint += x8_length; +} + +template +EffectReverb::EffectReverb(float coloration, float mix, float time, + float damping, float preDelay, double sampleRate) +: x140_x1c8_coloration(clamp(0.f, coloration, 1.f)), + x144_x1cc_mix(clamp(0.f, mix, 1.f)), + x148_x1d0_time(clamp(0.01f, time, 10.f)), + x14c_x1d4_damping(clamp(0.f, damping, 1.f)), + x150_x1d8_preDelay(clamp(0.f, preDelay, 0.1f)), + m_sampleRate(sampleRate) +{} + +template +void EffectReverb::_update() +{ + float timeSamples = x148_x1d0_time * m_sampleRate; + for (int c=0 ; c<8 ; ++c) + { + for (int t=0 ; t +void EffectReverb::applyEffect(T* audio, size_t frameCount, const ChannelMap& chanMap) +{ + if (m_dirty) + _update(); + + float dampWet = x118_x19c_level * 0.6f; + float dampDry = 0.6f - dampWet; + + for (size_t f=0 ; f(dampWet * allPass + dampDry * sample); + sample = audio[s * chanMap.m_channelCount + c]; + } + x130_x1b8_preDelayPtr[c] = preDelayPtr; + } + audio += chanMap.m_channelCount * 160; + } +} + +template +EffectReverbStd::EffectReverbStd(float coloration, float mix, float time, + float damping, float preDelay, double sampleRate) +: EffectReverb(coloration, mix, time, damping, preDelay, sampleRate) +{ + EffectReverb::_update(); +} + +template +void EffectReverbHi::_update() +{ + EffectReverb::_update(); + + for (int c=0 ; c<8 ; ++c) + { + ReverbDelayLine& hpLine = x78_LP[c]; + size_t tapDelay = LPTapDelays[c] * EffectReverb::m_sampleRate / 32000.0; + hpLine.allocate(tapDelay); + hpLine.setdelay(tapDelay); + } +} + +template +void EffectReverbHi::_handleReverb(T* audio, int c, int chanCount, int sampleCount) +{ + float dampWet = EffectReverb::x118_x19c_level * 0.6f; + float dampDry = 0.6f - dampWet; + + float* combCoefs = EffectReverb::xf4_x16c_combCoef[c]; + float& lpLastOut = EffectReverb::x10c_x190_lpLastout[c]; + float* preDelayLine = EffectReverb::x124_x1ac_preDelayLine[c].get(); + float* preDelayPtr = EffectReverb::x130_x1b8_preDelayPtr[c]; + float* lastPreDelaySamp = &preDelayLine[EffectReverb::x120_x1a4_preDelayTime - 1]; + + ReverbDelayLine* linesC = EffectReverb::x78_xb4_C[c]; + ReverbDelayLine* linesAP = EffectReverb::x0_x0_AP[c]; + ReverbDelayLine& lineHP = x78_LP[c]; + + float allPassCoef = EffectReverb::xf0_x168_allPassCoef; + float damping = EffectReverb::x11c_x1a0_damping; + int32_t preDelayTime = EffectReverb::x120_x1a4_preDelayTime; + + float sample = audio[c]; + for (int s=1 ; s(dampWet * allPass + dampDry * sample); + sample = audio[s * chanCount + c]; + } + EffectReverb::x130_x1b8_preDelayPtr[c] = preDelayPtr; +} + +template +void EffectReverbHi::_doCrosstalk(T* audio, float wet, float dry, int chanCount, int sampleCount) +{ + for (int i=0 ; i(base[c] + allWet); + } +} + +template +void EffectReverbHi::applyEffect(T* audio, size_t frameCount, const ChannelMap& chanMap) +{ + if (EffectReverb::m_dirty) + _update(); + + for (size_t f=0 ; f +EffectReverbHi::EffectReverbHi(float coloration, float mix, float time, + float damping, float preDelay, float crosstalk, + double sampleRate) +: EffectReverb(coloration, mix, time, damping, preDelay, sampleRate), + x1dc_crosstalk(clamp(0.f, crosstalk, 1.f)) +{ + _update(); +} + +template class EffectReverbStd; +template class EffectReverbStd; +template class EffectReverbStd; + +template class EffectReverbHi; +template class EffectReverbHi; +template class EffectReverbHi; + +} diff --git a/lib/EffectReverbHi.cpp b/lib/EffectReverbHi.cpp deleted file mode 100644 index e69de29..0000000 diff --git a/lib/EffectReverbStd.cpp b/lib/EffectReverbStd.cpp deleted file mode 100644 index e69de29..0000000 diff --git a/lib/Voice.cpp b/lib/Voice.cpp index 0725ac1..c344eff 100644 --- a/lib/Voice.cpp +++ b/lib/Voice.cpp @@ -366,6 +366,21 @@ void Voice::startSample(int16_t sampId, int32_t offset) m_lastSamplePos = m_curSample->first.m_loopLengthSamples ? (m_curSample->first.m_loopStartSample + m_curSample->first.m_loopLengthSamples) : m_curSample->first.m_numSamples; + _checkSamplePos(); + + /* Seek DSPADPCM state if needed */ + if (m_curSamplePos) + { + uint32_t block = m_curSamplePos / 14; + uint32_t rem = m_curSamplePos % 14; + for (uint32_t b = 0 ; b < block ; ++b) + DSPDecompressFrameStateOnly(m_curSampleData + 8 * b, m_curSample->second.m_coefs, + &m_prev1, &m_prev2, 14); + + if (rem) + DSPDecompressFrameStateOnly(m_curSampleData + 8 * block, m_curSample->second.m_coefs, + &m_prev1, &m_prev2, rem); + } } } diff --git a/lib/dsp.c b/lib/dsp.c index 30dec39..f3f678b 100644 --- a/lib/dsp.c +++ b/lib/dsp.c @@ -2,36 +2,6 @@ static const int32_t NibbleToInt[16] = {0,1,2,3,4,5,6,7,-8,-7,-6,-5,-4,-3,-2,-1}; -unsigned DSPDecompressFrameRanged(int16_t* out, const uint8_t* in, - const int16_t coefs[8][2], int16_t* prev1, int16_t* prev2, - unsigned firstSample, unsigned lastSample) -{ - uint8_t cIdx = (in[0]>>4) & 0xf; - int32_t factor1 = coefs[cIdx][0]; - int32_t factor2 = coefs[cIdx][1]; - uint8_t exp = in[0] & 0xf; - unsigned ret = 0; - for (int s=firstSample ; s<14 && s>4)&0xf]; - sampleData <<= exp; - sampleData <<= 11; - sampleData += 1024; - sampleData += - factor1 * ((int32_t)(*prev1)) + - factor2 * ((int32_t)(*prev2)); - sampleData >>= 11; - sampleData = DSPSampClamp(sampleData); - *out++ = sampleData; - *prev2 = *prev1; - *prev1 = sampleData; - ++ret; - } - return ret; -} - unsigned DSPDecompressFrame(int16_t* out, const uint8_t* in, const int16_t coefs[8][2], int16_t* prev1, int16_t* prev2, unsigned lastSample) @@ -73,7 +43,7 @@ unsigned DSPDecompressFrameStereoStride(int16_t* out, const uint8_t* in, unsigned ret = 0; for (int s=0 ; s<14 && s>4)&0xf]; sampleData <<= exp; @@ -103,7 +73,7 @@ unsigned DSPDecompressFrameStereoDupe(int16_t* out, const uint8_t* in, unsigned ret = 0; for (int s=0 ; s<14 && s>4)&0xf]; sampleData <<= exp; @@ -122,3 +92,62 @@ unsigned DSPDecompressFrameStereoDupe(int16_t* out, const uint8_t* in, } return ret; } + +unsigned DSPDecompressFrameRanged(int16_t* out, const uint8_t* in, + const int16_t coefs[8][2], int16_t* prev1, int16_t* prev2, + unsigned firstSample, unsigned lastSample) +{ + uint8_t cIdx = (in[0]>>4) & 0xf; + int32_t factor1 = coefs[cIdx][0]; + int32_t factor2 = coefs[cIdx][1]; + uint8_t exp = in[0] & 0xf; + unsigned ret = 0; + for (int s=firstSample ; s<14 && s>4)&0xf]; + sampleData <<= exp; + sampleData <<= 11; + sampleData += 1024; + sampleData += + factor1 * ((int32_t)(*prev1)) + + factor2 * ((int32_t)(*prev2)); + sampleData >>= 11; + sampleData = DSPSampClamp(sampleData); + *out++ = sampleData; + *prev2 = *prev1; + *prev1 = sampleData; + ++ret; + } + return ret; +} + +unsigned DSPDecompressFrameStateOnly(const uint8_t* in, + const int16_t coefs[8][2], int16_t* prev1, int16_t* prev2, + unsigned lastSample) +{ + uint8_t cIdx = (in[0]>>4) & 0xf; + int32_t factor1 = coefs[cIdx][0]; + int32_t factor2 = coefs[cIdx][1]; + uint8_t exp = in[0] & 0xf; + unsigned ret = 0; + for (int s=0 ; s<14 && s>4)&0xf]; + sampleData <<= exp; + sampleData <<= 11; + sampleData += 1024; + sampleData += + factor1 * ((int32_t)(*prev1)) + + factor2 * ((int32_t)(*prev2)); + sampleData >>= 11; + sampleData = DSPSampClamp(sampleData); + *prev2 = *prev1; + *prev1 = sampleData; + ++ret; + } + return ret; +}