Skip to content
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

Merged
merged 17 commits into from
May 20, 2015
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build/depends.py
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,7 @@ def sources(self, build):
"effects/native/moogladder4filtereffect.cpp",
"effects/native/reverbeffect.cpp",
"effects/native/echoeffect.cpp",
"effects/native/phasereffect.cpp",
"effects/native/reverb/Reverb.cc",

"engine/effects/engineeffectsmanager.cpp",
Expand Down
2 changes: 2 additions & 0 deletions src/effects/native/nativebackend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "effects/native/reverbeffect.h"
#endif
#include "effects/native/echoeffect.h"
#include "effects/native/phasereffect.h"

NativeBackend::NativeBackend(QObject* pParent)
: EffectsBackend(pParent, tr("Native")) {
Expand All @@ -33,6 +34,7 @@ NativeBackend::NativeBackend(QObject* pParent)
#ifndef __MACAPPSTORE__
registerEffect<ReverbEffect>();
#endif
registerEffect<PhaserEffect>();
}

NativeBackend::~NativeBackend() {
Expand Down
180 changes: 180 additions & 0 deletions src/effects/native/phasereffect.cpp
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"));
Copy link
Member

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.

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++;
Copy link
Member

Choose a reason for hiding this comment

The 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);
Copy link
Member

Choose a reason for hiding this comment

The 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?
It changes the content though the function. It would be nice to use a new speaking name every time the content changes. (The compiler will throw them out anyway)

Copy link
Member

Choose a reason for hiding this comment

The 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++) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the iir filter loop.
I would prefer to reformat it similar to this:
https://github.com/mixxxdj/mixxx/blob/master/src/engine/enginefilteriir.h#L268
Or this
https://github.com/mixxxdj/mixxx/blob/master/src/engine/enginefiltermoogladder4.h#L151
To have a reference to the used iir model.
This will help to recognize the form DF1 / DF2

Copy link
Member

Choose a reason for hiding this comment

The 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;
}
}
62 changes: 62 additions & 0 deletions src/effects/native/phasereffect.h
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