Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions build/depends.py
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,8 @@ def sources(self, build):
"effects/native/bessel8lvmixeqeffect.cpp",
"effects/native/threebandbiquadeqeffect.cpp",
"effects/native/biquadfullkilleqeffect.cpp",
"effects/native/semiparametriceq3knobeffect.cpp",
"effects/native/semiparametriceq4knobeffect.cpp",
"effects/native/loudnesscontoureffect.cpp",
"effects/native/graphiceqeffect.cpp",
"effects/native/flangereffect.cpp",
Expand Down
10 changes: 10 additions & 0 deletions src/effects/effectmanifest.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class EffectManifest final {
public:
EffectManifest()
: m_isMixingEQ(false),
m_isWaveformChangingEQ(false),
m_isMasterEQ(false),
m_effectRampsFromDry(false) {
}
Expand Down Expand Up @@ -82,6 +83,14 @@ class EffectManifest final {
m_isMixingEQ = value;
}

virtual const bool& isWaveformChangingEQ() const {
return m_isWaveformChangingEQ;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It is probably more universal usable if this is a per parameter option. Did you think about that?

If we find a broad consensus to ditch this fake feature. It's also a solution.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It is probably more universal usable if this is a per parameter option. Did you think about that?

I don't understand. In what situation would that be helpful?

If we find a broad consensus to ditch this fake feature. It's also a solution.

Huh? This is a really nice feature.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ok, so no consensus.
If we have a EQ, that for example had a button that effects the high frequencies, it can have a property on this parameter, that it changes the high gain.
In this case we have only the seeping Bell, which effects the mid range. So we may decide for a knob that visualises it in our waveform EQ fake.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

If we have a EQ, that for example had a button that effects the high frequencies, it can have a property on this parameter, that it changes the high gain.

But we do not have such an EQ effect that does that without effecting the mids and lows. If we add one in the future it would not be too difficult to add that capability, but there is no need for that here.

}

virtual void setIsWaveformChangingEQ(const bool value) {
m_isWaveformChangingEQ = value;
}

virtual const bool& isMasterEQ() const {
return m_isMasterEQ;
}
Expand Down Expand Up @@ -127,6 +136,7 @@ class EffectManifest final {
QString m_description;
// This helps us at DlgPrefEQ's basic selection of Equalizers
bool m_isMixingEQ;
bool m_isWaveformChangingEQ;
bool m_isMasterEQ;
QList<EffectManifestParameter> m_parameters;
bool m_effectRampsFromDry;
Expand Down
1 change: 1 addition & 0 deletions src/effects/effectmanifestparameter.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class EffectManifestParameter {
HERTZ,
SAMPLERATE, // fraction of the samplerate
BEATS, // multiples of a beat
DECIBELS,
};

enum class LinkType {
Expand Down
4 changes: 0 additions & 4 deletions src/effects/effectsmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,6 @@ const QList<EffectManifest> EffectsManager::getAvailableEffectManifestsFiltered(
return list;
}

bool EffectsManager::isEQ(const QString& effectId) const {
return getEffectManifest(effectId).isMixingEQ();
}

QString EffectsManager::getNextEffectId(const QString& effectId) {
if (m_availableEffectManifests.isEmpty()) {
return QString();
Expand Down
1 change: 0 additions & 1 deletion src/effects/effectsmanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ class EffectsManager : public QObject {
};
const QList<EffectManifest> getAvailableEffectManifestsFiltered(
EffectManifestFilterFnc filter) const;
bool isEQ(const QString& effectId) const;
QPair<EffectManifest, EffectsBackend*> getEffectManifestAndBackend(
const QString& effectId) const;
EffectManifest getEffectManifest(const QString& effectId) const;
Expand Down
2 changes: 2 additions & 0 deletions src/effects/native/equalizer_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ class EqualizerUtil {
public:
// Creates common EQ parameters like low/mid/high gain and kill buttons.
static void createCommonParameters(EffectManifest* manifest) {
manifest->setIsWaveformChangingEQ(true);

EffectManifestParameter* low = manifest->addParameter();
low->setId("low");
low->setName(QObject::tr("Low"));
Expand Down
4 changes: 4 additions & 0 deletions src/effects/native/nativebackend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#include "effects/native/bessel4lvmixeqeffect.h"
#include "effects/native/threebandbiquadeqeffect.h"
#include "effects/native/biquadfullkilleqeffect.h"
#include "effects/native/semiparametriceq3knobeffect.h"
#include "effects/native/semiparametriceq4knobeffect.h"
#include "effects/native/graphiceqeffect.h"
#include "effects/native/filtereffect.h"
#include "effects/native/moogladder4filtereffect.h"
Expand All @@ -29,6 +31,8 @@ NativeBackend::NativeBackend(QObject* pParent)
registerEffect<LinkwitzRiley8EQEffect>();
registerEffect<ThreeBandBiquadEQEffect>();
registerEffect<BiquadFullKillEQEffect>();
registerEffect<SemiparametricEQEffect3Knob>();
registerEffect<SemiparametricEQEffect4Knob>();
// Compensations EQs
registerEffect<GraphicEQEffect>();
registerEffect<LoudnessContourEffect>();
Expand Down
125 changes: 125 additions & 0 deletions src/effects/native/semiparametriceq3knobeffect.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#include "effects/native/semiparametriceq3knobeffect.h"

namespace {
static const int kStartupSamplerate = 44100;
static const double kMinCorner = 13; // Hz
static const double kMaxCorner = 22050; // Hz
static const double kLpfHpfQ = 0.1;
static const double kSemiparametricQ = 0.4;
static const double kSemiparametricMaxBoostDb = 8;
static const double kSemiparametricMaxCutDb = -20;
} // anonymous namespace

SemiparametricEQEffect3KnobGroupState::SemiparametricEQEffect3KnobGroupState()
: m_lowFilter(1, kMaxCorner / kStartupSamplerate, kLpfHpfQ, true),
m_semiParametricFilter(kStartupSamplerate, 1000, kSemiparametricQ),
m_highFilter(1, kMinCorner / kStartupSamplerate, kLpfHpfQ, true),
m_intermediateBuffer(MAX_BUFFER_LEN),
m_filterBehavior(kMinCorner, kMaxCorner, -40),
m_dCenterOld(0),
m_dGainOld(0),
m_dFilterOld(0) {
}

// static
QString SemiparametricEQEffect3Knob::getId() {
return "org.mixxx.effects.semiparametriceq3knob";
}

EffectManifest SemiparametricEQEffect3Knob::getManifest() {
EffectManifest manifest;
manifest.setId(getId());
manifest.setName(QObject::tr("Semiparametric Equalizer (3 knobs)"));
manifest.setShortName(QObject::tr("Semiparam 3"));
manifest.setAuthor("The Mixxx Team");
manifest.setVersion("1.0");
manifest.setDescription(QObject::tr(
"A semiparametric EQ effect modeled after the PLAYdifferently Model 1 hardware mixer."));
manifest.setEffectRampsFromDry(true);
manifest.setIsMixingEQ(true);

EffectManifestParameter* filter = manifest.addParameter();
filter->setId("filter");
filter->setName(QObject::tr("Filter"));
filter->setDescription(QObject::tr("Bipolar filter knob. Controls corner frequency ratio of the high pass filter on the left and corner frequency of the low pass filter on the right."));
filter->setControlHint(EffectManifestParameter::ControlHint::KNOB_LINEAR);
filter->setSemanticHint(EffectManifestParameter::SemanticHint::UNKNOWN);
filter->setUnitsHint(EffectManifestParameter::UnitsHint::UNKNOWN);
filter->setMinimum(0);
filter->setMaximum(1);
filter->setDefault(0.5);

EffectManifestParameter* gain = manifest.addParameter();
gain->setId("gain");
gain->setName(QObject::tr("Gain"));
gain->setDescription(QObject::tr("Gain of the semiparametric EQ"));
gain->setControlHint(EffectManifestParameter::ControlHint::KNOB_LOGARITHMIC);
gain->setSemanticHint(EffectManifestParameter::SemanticHint::UNKNOWN);
gain->setUnitsHint(EffectManifestParameter::UnitsHint::DECIBELS);
gain->setMinimum(0);
gain->setMaximum(4);
gain->setDefault(1);

EffectManifestParameter* center = manifest.addParameter();
center->setId("center");
center->setName(QObject::tr("Center"));
center->setDescription(QObject::tr("Center frequency of the semiparametric EQ"));
center->setControlHint(EffectManifestParameter::ControlHint::KNOB_LOGARITHMIC);
center->setSemanticHint(EffectManifestParameter::SemanticHint::UNKNOWN);
center->setUnitsHint(EffectManifestParameter::UnitsHint::HERTZ);
center->setMinimum(70);
center->setMaximum(7000);
center->setDefault(1000);

return manifest;
}

SemiparametricEQEffect3Knob::SemiparametricEQEffect3Knob(EngineEffect* pEffect,
const EffectManifest& manifest)
: m_pCenter(pEffect->getParameterById("center")),
m_pGain(pEffect->getParameterById("gain")),
m_pFilter(pEffect->getParameterById("filter")) {
Q_UNUSED(manifest);
}

void SemiparametricEQEffect3Knob::processChannel(const ChannelHandle& handle,
SemiparametricEQEffect3KnobGroupState* pState,
const CSAMPLE* pInput, CSAMPLE *pOutput,
const unsigned int numSamples,
const unsigned int sampleRate,
const EffectProcessor::EnableState enableState,
const GroupFeatureState& groupFeatureState) {
Q_UNUSED(handle);
Q_UNUSED(groupFeatureState);
Q_UNUSED(enableState);

double center = m_pCenter->value();
double gain = m_pGain->value();
double filter = m_pFilter->value();

if (center != pState->m_dCenterOld || gain != pState->m_dGainOld) {
double db = gain - 1.0;
if (db >= 0) {
db *= kSemiparametricMaxBoostDb;
} else {
db *= -kSemiparametricMaxCutDb;
}
pState->m_semiParametricFilter.setFrequencyCorners(sampleRate, center, kSemiparametricQ, db);
}
if (filter != pState->m_dFilterOld) {
double lpf = pState->m_filterBehavior.parameterToValue(
std::min((filter * 2.0), 1.0)) / sampleRate;
double hpf = pState->m_filterBehavior.parameterToValue(
std::max((filter - 0.5) * 2.0, 0.0)) / sampleRate;
pState->m_lowFilter.setFrequencyCorners(1, lpf, kLpfHpfQ);
pState->m_highFilter.setFrequencyCorners(1, hpf, kLpfHpfQ);
}

pState->m_lowFilter.process(pInput, pState->m_intermediateBuffer.data(), numSamples);
pState->m_semiParametricFilter.process(pState->m_intermediateBuffer.data(), pState->m_intermediateBuffer.data(), numSamples);
pState->m_highFilter.process(pState->m_intermediateBuffer.data(), pOutput, numSamples);

pState->m_dCenterOld = center;
pState->m_dGainOld = gain;
pState->m_dFilterOld = filter;
}
65 changes: 65 additions & 0 deletions src/effects/native/semiparametriceq3knobeffect.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#ifndef SEMIPARAMETRICEQ3KNOB_H
#define SEMIPARAMETRICEQ3KNOB_H

#include "control/controlbehavior.h"
#include "effects/effect.h"
#include "effects/effectprocessor.h"
#include "engine/effects/engineeffect.h"
#include "engine/effects/engineeffectparameter.h"
#include "engine/enginefilterbiquad1.h"
#include "util/class.h"
#include "util/defs.h"
#include "util/memory.h"
#include "util/sample.h"
#include "util/samplebuffer.h"
#include "util/types.h"

struct SemiparametricEQEffect3KnobGroupState {
public:
SemiparametricEQEffect3KnobGroupState();
inline ~SemiparametricEQEffect3KnobGroupState() {};

EngineFilterBiquad1Low m_lowFilter;
EngineFilterBiquad1Peaking m_semiParametricFilter;
EngineFilterBiquad1High m_highFilter;

SampleBuffer m_intermediateBuffer;
ControlLogPotmeterBehavior m_filterBehavior;

double m_dCenterOld;
double m_dGainOld;
double m_dFilterOld;
};

class SemiparametricEQEffect3Knob : public PerChannelEffectProcessor<SemiparametricEQEffect3KnobGroupState> {
public:
SemiparametricEQEffect3Knob(EngineEffect* pEffect, const EffectManifest& manifest);
inline ~SemiparametricEQEffect3Knob() {};

static QString getId();
static EffectManifest getManifest();

// See effectprocessor.h
void processChannel(const ChannelHandle& handle,
SemiparametricEQEffect3KnobGroupState* pState,
const CSAMPLE* pInput, CSAMPLE *pOutput,
const unsigned int numSamples,
const unsigned int sampleRate,
const EffectProcessor::EnableState enableState,
const GroupFeatureState& groupFeatureState);

private:
QString debugString() const {
return getId();
}

EngineEffectParameter* m_pCenter;
EngineEffectParameter* m_pGain;
EngineEffectParameter* m_pFilter;

unsigned int m_oldSampleRate;

DISALLOW_COPY_AND_ASSIGN(SemiparametricEQEffect3Knob);
};

#endif // SEMIPARAMETRICEQ3KNOB_H
Loading