diff --git a/build/depends.py b/build/depends.py index 576269fd0c3..42930a44f90 100644 --- a/build/depends.py +++ b/build/depends.py @@ -558,6 +558,7 @@ def sources(self, build): "effects/native/bitcrushereffect.cpp", "effects/native/flangereffect.cpp", "effects/native/filtereffect.cpp", + "effects/native/reverbeffect.cpp", "engine/effects/engineeffectsmanager.cpp", "engine/effects/engineeffectchain.cpp", diff --git a/src/effects/effectsmanager.cpp b/src/effects/effectsmanager.cpp index 5c3195b8115..ef116ba080f 100644 --- a/src/effects/effectsmanager.cpp +++ b/src/effects/effectsmanager.cpp @@ -97,25 +97,33 @@ void EffectsManager::setupDefaults() { this, "org.mixxx.effectchain.flanger")); pChain->setName(tr("Flanger")); pChain->setParameter(0.0f); - EffectPointer flanger = instantiateEffect( + EffectPointer pEffect = instantiateEffect( "org.mixxx.effects.flanger"); - pChain->addEffect(flanger); + pChain->addEffect(pEffect); m_pEffectChainManager->addEffectChain(pChain); pChain = EffectChainPointer(new EffectChain( this, "org.mixxx.effectchain.bitcrusher")); pChain->setName(tr("BitCrusher")); pChain->setParameter(0.0f); - flanger = instantiateEffect("org.mixxx.effects.bitcrusher"); - pChain->addEffect(flanger); + pEffect = instantiateEffect("org.mixxx.effects.bitcrusher"); + pChain->addEffect(pEffect); m_pEffectChainManager->addEffectChain(pChain); pChain = EffectChainPointer(new EffectChain( this, "org.mixxx.effectchain.filter")); pChain->setName(tr("Filter")); pChain->setParameter(0.0f); - flanger = instantiateEffect("org.mixxx.effects.filter"); - pChain->addEffect(flanger); + pEffect = instantiateEffect("org.mixxx.effects.filter"); + pChain->addEffect(pEffect); + m_pEffectChainManager->addEffectChain(pChain); + + pChain = EffectChainPointer(new EffectChain( + this, "org.mixxx.effectchain.reverb")); + pChain->setName(tr("Reverb")); + pChain->setParameter(0.0f); + pEffect = instantiateEffect("org.mixxx.effects.reverb"); + pChain->addEffect(pEffect); m_pEffectChainManager->addEffectChain(pChain); } diff --git a/src/effects/native/nativebackend.cpp b/src/effects/native/nativebackend.cpp index 00a4fbd20d3..2b6b9b7e77f 100644 --- a/src/effects/native/nativebackend.cpp +++ b/src/effects/native/nativebackend.cpp @@ -4,12 +4,14 @@ #include "effects/native/flangereffect.h" #include "effects/native/bitcrushereffect.h" #include "effects/native/filtereffect.h" +#include "effects/native/reverbeffect.h" NativeBackend::NativeBackend(QObject* pParent) : EffectsBackend(pParent, tr("Native")) { registerEffect(); registerEffect(); registerEffect(); + registerEffect(); } NativeBackend::~NativeBackend() { diff --git a/src/effects/native/reverbeffect.cpp b/src/effects/native/reverbeffect.cpp new file mode 100644 index 00000000000..c8a607d007f --- /dev/null +++ b/src/effects/native/reverbeffect.cpp @@ -0,0 +1,136 @@ +#include + +#include "effects/native/reverbeffect.h" +#include "effects/native/waveguide_nl.h" + +#include "mathstuff.h" +#include "sampleutil.h" + +#define RUN_WG(n, junct_a, junct_b) waveguide_nl_process_lin(group_state.waveguide[n], junct_a - group_state.out[n*2+1], junct_b - group_state.out[n*2], group_state.out+n*2, group_state.out+n*2+1) + +// static +QString ReverbEffect::getId() { + return "org.mixxx.effects.reverb"; +} + +// static +EffectManifest ReverbEffect::getManifest() { + EffectManifest manifest; + manifest.setId(getId()); + manifest.setName(QObject::tr("Reverb")); + manifest.setAuthor("The Mixxx Team, SWH Plugins"); + manifest.setVersion("1.0"); + manifest.setDescription("TODO"); + + EffectManifestParameter* time = manifest.addParameter(); + time->setId("time"); + time->setName(QObject::tr("time")); + time->setDescription(QObject::tr("Controls the RT60 time of the reverb. " + "Actually controls the size of the plate. The mapping " + "between plate size and RT60 time is just a " + "heuristic, so it's not very accurate.")); + time->setControlHint(EffectManifestParameter::CONTROL_KNOB_LINEAR); + time->setValueHint(EffectManifestParameter::VALUE_FLOAT); + time->setSemanticHint(EffectManifestParameter::SEMANTIC_UNKNOWN); + time->setUnitsHint(EffectManifestParameter::UNITS_UNKNOWN); + time->setMinimum(0.1); + time->setDefault(1.0); + time->setMaximum(8.5); + + EffectManifestParameter* damping = manifest.addParameter(); + damping->setId("damping"); + damping->setName(QObject::tr("damping")); + damping->setDescription(QObject::tr("Controls the degree that the surface " + "of the plate is damped.")); + damping->setControlHint(EffectManifestParameter::CONTROL_KNOB_LINEAR); + damping->setValueHint(EffectManifestParameter::VALUE_FLOAT); + damping->setSemanticHint(EffectManifestParameter::SEMANTIC_UNKNOWN); + damping->setUnitsHint(EffectManifestParameter::UNITS_UNKNOWN); + damping->setMinimum(0.0); + damping->setDefault(0.5); + damping->setMaximum(1.0); + + return manifest; +} + +ReverbEffect::ReverbEffect(EngineEffect* pEffect, + const EffectManifest& manifest) + : m_pTimeParameter(pEffect->getParameterById("time")), + m_pDampingParameter(pEffect->getParameterById("damping")) { +} + +ReverbEffect::~ReverbEffect() { + for (QMap::iterator it = m_groupState.begin(); + it != m_groupState.end();) { + for (int i = 0; i < 8; i++) { + waveguide_nl_reset((*it)->waveguide[i]); + } + SampleUtil::free((*it)->out); + delete it.value(); + it = m_groupState.erase(it); + } + qDebug() << debugString() << "destroyed"; +} + +void ReverbEffect::process(const QString& group, + const CSAMPLE* pInput, CSAMPLE* pOutput, + const unsigned int numSamples) { + GroupState* pGroupState = m_groupState.value(group, NULL); + if (pGroupState == NULL) { + pGroupState = new GroupState(); + m_groupState[group] = pGroupState; + } + GroupState& group_state = *pGroupState; + + CSAMPLE time = m_pTimeParameter ? + m_pTimeParameter->value().toDouble() : 1.0f; + CSAMPLE damping = m_pDampingParameter ? + m_pDampingParameter->value().toDouble() : 0.5f; + + // TODO(owilliams) check ranges? + + unsigned long pos; + const float scale = powf(time * 0.117647f, 1.34f); + const float lpscale = 1.0f - damping * 0.93; + + for (pos=0; pos<8; pos++) { + waveguide_nl_set_delay(group_state.waveguide[pos], + group_state.waveguide[pos]->size * scale); + } + for (pos=0; pos<4; pos++) { + waveguide_nl_set_fc(group_state.waveguide[pos], LP_INNER * lpscale); + } + for (; pos<8; pos++) { + waveguide_nl_set_fc(group_state.waveguide[pos], LP_OUTER * lpscale); + } + + for (pos = 0; pos < numSamples - 1; pos+=2) { + const float alpha = + (group_state.out[0] + group_state.out[2] + + group_state.out[4] + group_state.out[6]) * 0.5f + pInput[pos]; + const float beta = + (group_state.out[1] + group_state.out[9] + group_state.out[14]) + * 0.666666666f; + const float gamma = + (group_state.out[3] + group_state.out[8] + group_state.out[11]) + * 0.666666666f; + const float delta = + (group_state.out[5] + group_state.out[10] + group_state.out[13]) + * 0.666666666f; + const float epsilon = + (group_state.out[7] + group_state.out[12] + group_state.out[15]) + * 0.666666666f; + + RUN_WG(0, beta, alpha); + RUN_WG(1, gamma, alpha); + RUN_WG(2, delta, alpha); + RUN_WG(3, epsilon, alpha); + RUN_WG(4, beta, gamma); + RUN_WG(5, gamma, delta); + RUN_WG(6, delta, epsilon); + RUN_WG(7, epsilon, beta); + + pOutput[pos] = beta + pInput[pos]; + pOutput[pos + 1] = beta + pInput[pos + 1]; + } +} diff --git a/src/effects/native/reverbeffect.h b/src/effects/native/reverbeffect.h new file mode 100644 index 00000000000..81ce62d5e68 --- /dev/null +++ b/src/effects/native/reverbeffect.h @@ -0,0 +1,64 @@ +// Ported from SWH Plate Reverb 1423. +// This effect is GPL code. + +#ifndef REVERBEFFECT_H +#define REVERBEFFECT_H + +#include + +#include "defs.h" +#include "util.h" +#include "engine/effects/engineeffect.h" +#include "engine/effects/engineeffectparameter.h" +#include "effects/effectprocessor.h" +#include "effects/native/waveguide_nl.h" +#include "sampleutil.h" + +#define LP_INNER 0.96f +#define LP_OUTER 0.983f +const unsigned int kOutBufSize = 32; + +class ReverbEffect : public EffectProcessor { + public: + ReverbEffect(EngineEffect* pEffect, const EffectManifest& manifest); + virtual ~ReverbEffect(); + + static QString getId(); + static EffectManifest getManifest(); + + // See effectprocessor.h + void process(const QString& group, + const CSAMPLE* pInput, CSAMPLE* pOutput, + const unsigned int numSamples); + + private: + QString debugString() const { + return getId(); + } + + EngineEffectParameter* m_pTimeParameter; + EngineEffectParameter* m_pDampingParameter; + + struct GroupState { + GroupState() { + waveguide = (waveguide_nl**)malloc(8 * sizeof(waveguide_nl *)); + waveguide[0] = waveguide_nl_new(2389, LP_INNER, 0.04f, 0.0f); + waveguide[1] = waveguide_nl_new(4742, LP_INNER, 0.17f, 0.0f); + waveguide[2] = waveguide_nl_new(4623, LP_INNER, 0.52f, 0.0f); + waveguide[3] = waveguide_nl_new(2142, LP_INNER, 0.48f, 0.0f); + waveguide[4] = waveguide_nl_new(5597, LP_OUTER, 0.32f, 0.0f); + waveguide[5] = waveguide_nl_new(3692, LP_OUTER, 0.89f, 0.0f); + waveguide[6] = waveguide_nl_new(5611, LP_OUTER, 0.28f, 0.0f); + waveguide[7] = waveguide_nl_new(3703, LP_OUTER, 0.29f, 0.0f); + + out = SampleUtil::alloc(kOutBufSize); + } + waveguide_nl** waveguide; + CSAMPLE* out; + }; + QMap m_groupState; + + DISALLOW_COPY_AND_ASSIGN(ReverbEffect); +}; + +#endif /* REVERBEFFECT_H */ diff --git a/src/effects/native/waveguide_nl.h b/src/effects/native/waveguide_nl.h new file mode 100644 index 00000000000..dbeca20e191 --- /dev/null +++ b/src/effects/native/waveguide_nl.h @@ -0,0 +1,148 @@ +#ifndef WAVEGUIDE_NL_H +#define WAVEGUIDE_NL_H + +#include +#include + +typedef struct { + int size; + float *buffer[2]; + int ptr; + int delay; + float fc; + float lp[2]; + float a1a; + float a1b; + float zm1[2]; +} waveguide_nl; + +inline waveguide_nl *waveguide_nl_new(int size, float fc, float da, float db) +{ + waveguide_nl *wg = (waveguide_nl*)malloc(sizeof(waveguide_nl)); + wg->size = size; + wg->delay = size; + wg->buffer[0] = (float*)calloc(size, sizeof(float)); + wg->buffer[1] = (float*)calloc(size, sizeof(float)); + wg->ptr = 0; + wg->fc = fc; + wg->lp[0] = 0.0f; + wg->lp[1] = 0.0f; + wg->zm1[0] = 0.0f; + wg->zm1[1] = 0.0f; + wg->a1a = (1.0f - da) / (1.0f + da); + wg->a1b = (1.0f - db) / (1.0f + db); + + return wg; +} + +inline void waveguide_nl_reset(waveguide_nl *wg) +{ + memset(wg->buffer[0], 0, wg->size * sizeof(float)); + memset(wg->buffer[1], 0, wg->size * sizeof(float)); + wg->lp[0] = 0.0f; + wg->lp[1] = 0.0f; + wg->zm1[0] = 0.0f; + wg->zm1[1] = 0.0f; +} + +inline void waveguide_nl_free(waveguide_nl *wg) +{ + if (!wg) { + return; + } + free(wg->buffer[0]); + free(wg->buffer[1]); + free(wg); +} + +inline void waveguide_nl_set_delay(waveguide_nl *wg, int delay) +{ + if (delay > wg->size) { + wg->delay = wg->size; + } else if (delay < 1) { + wg->delay = 1; + } else { + wg->delay = delay; + } +} + +inline void waveguide_nl_set_fc(waveguide_nl *wg, float fc) +{ + wg->fc = fc; +} + +inline void waveguide_nl_set_ap(waveguide_nl *wg, float da, float db) +{ + wg->a1a = (1.0f - da) / (1.0f + da); + wg->a1b = (1.0f - db) / (1.0f + db); +} + +inline void waveguide_nl_process_lin(waveguide_nl *wg, float in0, float in1, float *out0, float *out1) +{ + float tmp; + + *out0 = wg->buffer[0][(wg->ptr + wg->delay) % wg->size]; + *out0 = wg->lp[0] * (wg->fc - 1.0f) + wg->fc * *out0; + wg->lp[0] = *out0; + tmp = *out0 * -(wg->a1a) + wg->zm1[0]; + wg->zm1[0] = tmp * wg->a1a + *out0; + *out0 = tmp; + + *out1 = wg->buffer[1][(wg->ptr + wg->delay) % wg->size]; + *out1 = wg->lp[1] * (wg->fc - 1.0f) + wg->fc * *out1; + wg->lp[1] = *out1; + tmp = *out1 * -(wg->a1a) + wg->zm1[1]; + wg->zm1[1] = tmp * wg->a1a + *out1; + *out1 = tmp; + + wg->buffer[0][wg->ptr] = in0; + wg->buffer[1][wg->ptr] = in1; + wg->ptr--; + if (wg->ptr < 0) { + wg->ptr += wg->size; + } +} + +inline void waveguide_nl_process(waveguide_nl *wg, float in0, float in1, float *out0, float *out1) +{ + float tmp; + float a1; + float b; + + *out0 = wg->buffer[0][(wg->ptr + wg->delay) % wg->size]; + *out0 = wg->lp[0] * (wg->fc - 1.0f) + wg->fc * *out0; + wg->lp[0] = *out0; + b = (*out0 + 1.0) * 6.0f; + if (b > 1.0f) { + b = 1.0f; + } else if (b < 0.0f) { + b = 0.0f; + } + a1 = b * wg->a1a + (1.0f - b) * wg->a1b; + tmp = *out0 * -a1 + wg->zm1[0]; + wg->zm1[0] = tmp * a1 + *out0; + *out0 = tmp; + + *out1 = wg->buffer[1][(wg->ptr + wg->delay) % wg->size]; + *out1 = wg->lp[1] * (wg->fc - 1.0f) + wg->fc * *out1; + wg->lp[1] = *out1; + b = (*out1 + 1.0) * 6.0f; + if (b > 1.0f) { + b = 1.0f; + } else if (b < 0.0f) { + b = 0.0f; + } + a1 = b * wg->a1a + (1.0f - b) * wg->a1b; + tmp = *out1 * -a1 + wg->zm1[1]; + wg->zm1[1] = tmp * a1 + *out1; + *out1 = tmp; + + wg->buffer[0][wg->ptr] = in0; + wg->buffer[1][wg->ptr] = in1; + wg->ptr--; + if (wg->ptr < 0) { + wg->ptr += wg->size; + } +} + +#endif