-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Bug #1299029] phaser effect #560
Changes from 6 commits
55ff445
014f479
a6b7a5d
c08f96c
fe02d65
322271f
3bfa4a7
77302f9
b5f198b
5e628e8
24eabfb
a52f1ea
bb5b6f7
9c2ce17
c71c878
5e4cb83
8bc8e3c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
#include <QDebug> | ||
#include "util/math.h" | ||
#include "effects/native/phasereffect.h" | ||
#include <stdlib.h> | ||
|
||
using namespace std; | ||
|
||
// static | ||
QString PhaserEffect::getId() { | ||
return "org.mixxx.effects.phaser"; | ||
} | ||
|
||
// static | ||
EffectManifest PhaserEffect::getManifest() { | ||
EffectManifest manifest; | ||
manifest.setId(getId()); | ||
manifest.setName(QObject::tr("Phaser")); | ||
manifest.setAuthor("The Mixxx Team"); | ||
manifest.setVersion("1.0"); | ||
manifest.setDescription(QObject::tr( | ||
"A more complex sound effect obtained by mixing the input signal" | ||
"with a copy passed through a series of all-pass filters.")); | ||
|
||
EffectManifestParameter* stages = manifest.addParameter(); | ||
stages->setId("stages"); | ||
stages->setName(QObject::tr("Stages")); | ||
stages->setDescription("Sets number of stages."); | ||
stages->setControlHint(EffectManifestParameter::CONTROL_KNOB_LINEAR); | ||
stages->setSemanticHint(EffectManifestParameter::SEMANTIC_UNKNOWN); | ||
stages->setUnitsHint(EffectManifestParameter::UNITS_UNKNOWN); | ||
stages->setDefault(2.0); | ||
stages->setMinimum(2.0); | ||
stages->setMaximum(24.0); | ||
|
||
EffectManifestParameter* frequency = manifest.addParameter(); | ||
frequency->setId("frequency"); | ||
frequency->setName(QObject::tr("Frequency")); | ||
frequency->setDescription("Controls frequency."); | ||
frequency->setControlHint(EffectManifestParameter::CONTROL_KNOB_LINEAR); | ||
frequency->setSemanticHint(EffectManifestParameter::SEMANTIC_UNKNOWN); | ||
frequency->setUnitsHint(EffectManifestParameter::UNITS_UNKNOWN); | ||
frequency->setDefault(0.5); | ||
frequency->setMinimum(0.0); | ||
frequency->setMaximum(5.0); | ||
|
||
EffectManifestParameter* depth = manifest.addParameter(); | ||
depth->setId("depth"); | ||
depth->setName(QObject::tr("Depth")); | ||
depth->setDescription("Controls depth."); | ||
depth->setControlHint(EffectManifestParameter::CONTROL_KNOB_LINEAR); | ||
depth->setSemanticHint(EffectManifestParameter::SEMANTIC_UNKNOWN); | ||
depth->setUnitsHint(EffectManifestParameter::UNITS_UNKNOWN); | ||
depth->setDefault(1.0); | ||
depth->setMinimum(0.0); | ||
depth->setMaximum(1.0); | ||
|
||
EffectManifestParameter* fb = manifest.addParameter(); | ||
fb->setId("feedback"); | ||
fb->setName(QObject::tr("Feedback")); | ||
fb->setDescription("Feedback"); | ||
fb->setControlHint(EffectManifestParameter::CONTROL_KNOB_LINEAR); | ||
fb->setSemanticHint(EffectManifestParameter::SEMANTIC_UNKNOWN); | ||
fb->setUnitsHint(EffectManifestParameter::UNITS_UNKNOWN); | ||
fb->setDefault(0.5); | ||
fb->setMinimum(0.0); | ||
fb->setMaximum(1.0); | ||
|
||
EffectManifestParameter* sweep = manifest.addParameter(); | ||
sweep->setId("sweep_width"); | ||
sweep->setName(QObject::tr("Sweep")); | ||
sweep->setDescription("Sets sweep width."); | ||
sweep->setControlHint(EffectManifestParameter::CONTROL_KNOB_LINEAR); | ||
sweep->setSemanticHint(EffectManifestParameter::SEMANTIC_UNKNOWN); | ||
sweep->setUnitsHint(EffectManifestParameter::UNITS_UNKNOWN); | ||
sweep->setDefault(0.5); | ||
sweep->setMinimum(0.0); | ||
sweep->setMaximum(1.0); | ||
|
||
EffectManifestParameter* stereo = manifest.addParameter(); | ||
stereo->setId("stereo"); | ||
stereo->setName(QObject::tr("Stereo")); | ||
stereo->setDescription(QObject::tr("Enables stereo")); | ||
stereo->setControlHint(EffectManifestParameter::CONTROL_TOGGLE_STEPPING); | ||
stereo->setSemanticHint(EffectManifestParameter::SEMANTIC_UNKNOWN); | ||
stereo->setUnitsHint(EffectManifestParameter::UNITS_UNKNOWN); | ||
stereo->setDefault(0); | ||
stereo->setMinimum(0); | ||
stereo->setMaximum(1); | ||
return manifest; | ||
} | ||
|
||
PhaserEffect::PhaserEffect(EngineEffect* pEffect, | ||
const EffectManifest& manifest) | ||
: m_pStagesParameter(pEffect->getParameterById("stages")), | ||
m_pFrequencyParameter(pEffect->getParameterById("frequency")), | ||
m_pDepthParameter(pEffect->getParameterById("depth")), | ||
m_pFeedbackParameter(pEffect->getParameterById("feedback")), | ||
m_pSweepWidthParameter(pEffect->getParameterById("sweep_width")), | ||
m_pStereoParameter(pEffect->getParameterById("stereo")) { | ||
Q_UNUSED(manifest); | ||
} | ||
|
||
PhaserEffect::~PhaserEffect() { | ||
//qDebug() << debugString() << "destroyed"; | ||
} | ||
|
||
void PhaserEffect::processChannel(const ChannelHandle& handle, | ||
PhaserGroupState* pState, | ||
const CSAMPLE* pInput, CSAMPLE* pOutput, | ||
const unsigned int numSamples, | ||
const unsigned int sampleRate, | ||
const EffectProcessor::EnableState enableState, | ||
const GroupFeatureState& groupFeatures) { | ||
|
||
Q_UNUSED(handle); | ||
Q_UNUSED(enableState); | ||
Q_UNUSED(groupFeatures); | ||
Q_UNUSED(sampleRate); | ||
|
||
CSAMPLE frequency = m_pFrequencyParameter->value(); | ||
CSAMPLE depth = m_pDepthParameter->value(); | ||
CSAMPLE feedback = m_pFeedbackParameter->value(); | ||
CSAMPLE sweepWidth = m_pSweepWidthParameter->value(); | ||
int stages = m_pStagesParameter->value(); | ||
|
||
CSAMPLE* oldInLeft = pState->oldInLeft; | ||
CSAMPLE* oldOutLeft = pState->oldOutLeft; | ||
CSAMPLE* oldInRight = pState->oldInRight; | ||
CSAMPLE* oldOutRight = pState->oldOutRight; | ||
|
||
CSAMPLE filterCoefLeft = 0; | ||
CSAMPLE filterCoefRight = 0; | ||
|
||
CSAMPLE left = 0, right = 0; | ||
CSAMPLE leftPhase, rightPhase; | ||
CSAMPLE freqSkip = frequency * 2.0 * M_PI / sampleRate; | ||
|
||
int stereoCheck = m_pStereoParameter->value(); | ||
int counter = 0; | ||
int updateCoef = 16; | ||
|
||
const int kChannels = 2; | ||
for (unsigned int i = 0; i < numSamples; i += kChannels) { | ||
|
||
pState->time++; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Time is growing infinite and will finally overflow. You should store the phase instead and add a phase fraction per sample. This will also avoid the bubbling noise. |
||
left = pInput[i] + left * feedback; | ||
right = pInput[i + 1] + right * feedback; | ||
|
||
leftPhase = fmodf(freqSkip * pState->time, 2.0 * M_PI); | ||
rightPhase = fmodf(freqSkip * pState->time + M_PI * stereoCheck, 2.0 * M_PI); | ||
|
||
if ((counter++) % updateCoef == 0) { | ||
CSAMPLE delayLeft = 0.5 + 0.5 * sin(leftPhase); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This part of the code is confusing. What is delayLeft, really a sample value, or a time? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does sweepWidth? |
||
CSAMPLE delayRight = 0.5 + 0.5 * sin(rightPhase); | ||
|
||
delayLeft = min((double)(sweepWidth * delayLeft), 0.99 * M_PI); | ||
delayRight = min((double)(sweepWidth * delayRight), 0.99 * M_PI); | ||
|
||
delayLeft = tan(delayLeft / 2); | ||
delayRight = tan(delayRight / 2); | ||
|
||
filterCoefLeft = (1.0 - delayLeft) / (1.0 + delayLeft); | ||
filterCoefRight = (1.0 - delayRight) / (1.0 + delayRight); | ||
} | ||
|
||
for (int j = 0; j < stages; j++) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the iir filter loop. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You have "Direct Form 1" |
||
oldOutLeft[j] = (filterCoefLeft * left) + | ||
(filterCoefLeft * oldOutLeft[j]) - oldInLeft[j]; | ||
oldInLeft[j] = left; | ||
left = oldOutLeft[j]; | ||
|
||
oldOutRight[j] = (filterCoefRight * right) + | ||
(filterCoefRight * oldOutRight[j]) - oldInRight[j]; | ||
oldInRight[j] = right; | ||
right = oldOutRight[j]; | ||
} | ||
pOutput[i] = pInput[i] * (1.0 - 0.5 * depth) + left * depth * 0.5; | ||
pOutput[i + 1] = pInput[i + 1] * (1.0 - 0.5 * depth) + right * depth * 0.5; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
#ifndef PHASEREFFECT_H | ||
#define PHASEREFFECT_H | ||
|
||
#include "util.h" | ||
#include "util/defs.h" | ||
#include "util/types.h" | ||
#include "engine/effects/engineeffect.h" | ||
#include "engine/effects/engineeffectparameter.h" | ||
#include "effects/effectprocessor.h" | ||
#include "sampleutil.h" | ||
|
||
#define MAXSTAGES 24 | ||
|
||
struct PhaserGroupState { | ||
PhaserGroupState() : | ||
time(0) { | ||
SampleUtil::applyGain(oldInLeft, 0, MAXSTAGES); | ||
SampleUtil::applyGain(oldOutLeft, 0, MAXSTAGES); | ||
SampleUtil::applyGain(oldInRight, 0, MAXSTAGES); | ||
SampleUtil::applyGain(oldOutRight, 0, MAXSTAGES); | ||
} | ||
CSAMPLE oldInLeft[MAXSTAGES]; | ||
CSAMPLE oldInRight[MAXSTAGES]; | ||
CSAMPLE oldOutLeft[MAXSTAGES]; | ||
CSAMPLE oldOutRight[MAXSTAGES]; | ||
int time; | ||
}; | ||
|
||
class PhaserEffect : public PerChannelEffectProcessor<PhaserGroupState> { | ||
|
||
public: | ||
PhaserEffect(EngineEffect* pEffect, const EffectManifest& manifest); | ||
virtual ~PhaserEffect(); | ||
|
||
static QString getId(); | ||
static EffectManifest getManifest(); | ||
|
||
// See effectprocessor.h | ||
void processChannel(const ChannelHandle& handle, | ||
PhaserGroupState* pState, | ||
const CSAMPLE* pInput, CSAMPLE* pOutput, | ||
const unsigned int numSamples, | ||
const unsigned int sampleRate, | ||
const EffectProcessor::EnableState enableState, | ||
const GroupFeatureState& groupFeatures); | ||
|
||
private: | ||
QString debugString() const { | ||
return getId(); | ||
} | ||
|
||
EngineEffectParameter* m_pStagesParameter; | ||
EngineEffectParameter* m_pFrequencyParameter; | ||
EngineEffectParameter* m_pDepthParameter; | ||
EngineEffectParameter* m_pFeedbackParameter; | ||
EngineEffectParameter* m_pSweepWidthParameter; | ||
EngineEffectParameter* m_pStereoParameter; | ||
|
||
DISALLOW_COPY_AND_ASSIGN(PhaserEffect); | ||
}; | ||
|
||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you. This works for me for now. Maybe we can set the shift between L and R later by a knob.