Skip to content

Commit

Permalink
Added trans effect.
Browse files Browse the repository at this point in the history
  • Loading branch information
SB committed Jul 24, 2021
1 parent 60a5845 commit 3dff55a
Show file tree
Hide file tree
Showing 4 changed files with 243 additions and 0 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,7 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL
src/effects/builtin/bitcrushereffect.cpp
src/effects/builtin/builtinbackend.cpp
src/effects/builtin/echoeffect.cpp
src/effects/builtin/transeffect.cpp
src/effects/builtin/filtereffect.cpp
src/effects/builtin/flangereffect.cpp
src/effects/builtin/graphiceqeffect.cpp
Expand Down
2 changes: 2 additions & 0 deletions src/effects/builtin/builtinbackend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "effects/builtin/loudnesscontoureffect.h"
#include "effects/builtin/metronomeeffect.h"
#include "effects/builtin/phasereffect.h"
#include "effects/builtin/transeffect.h"
#include "effects/builtin/tremoloeffect.h"
#include "effects/builtin/whitenoiseeffect.h"

Expand All @@ -47,6 +48,7 @@ BuiltInBackend::BuiltInBackend(QObject* pParent)
// Fancy effects
registerEffect<FlangerEffect>();
registerEffect<EchoEffect>();
registerEffect<TransEffect>();
registerEffect<AutoPanEffect>();
#ifndef __MACAPPSTORE__
registerEffect<ReverbEffect>();
Expand Down
185 changes: 185 additions & 0 deletions src/effects/builtin/transeffect.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
#include "effects/builtin/transeffect.h"

#include "util/rampingvalue.h"
#include "util/sample.h"

namespace {
const CSAMPLE dTransFadeTime = CSAMPLE(0.025);
}

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

// static
EffectManifestPointer TransEffect::getManifest() {
EffectManifestPointer pManifest(new EffectManifest());

pManifest->setId(getId());
pManifest->setName(QObject::tr("Trans"));
pManifest->setAuthor("The Mixxx Team");
pManifest->setVersion("1.0");
pManifest->setDescription(QObject::tr("Trans effect"));

EffectManifestParameterPointer period = pManifest->addParameter();
period->setId("period");
period->setName(QObject::tr("Period"));
period->setDescription(QObject::tr("Trans period (1/period = repetitions per beat when quantized / otherwise period time in seconds)."));
period->setControlHint(EffectManifestParameter::ControlHint::KNOB_LOGARITHMIC);
period->setSemanticHint(EffectManifestParameter::SemanticHint::UNKNOWN);
period->setUnitsHint(EffectManifestParameter::UnitsHint::TIME);
period->setMinimum(0.05);
period->setDefault(0.1);
period->setMaximum(2.0);

EffectManifestParameterPointer fade_time_frac = pManifest->addParameter();
fade_time_frac->setId("fadetime");
fade_time_frac->setName(QObject::tr("Fade Time Fraction"));
fade_time_frac->setDescription(QObject::tr("Fraction of time for fade in/out: 100% = 50%% fade-in + 50%% fade-out."));
fade_time_frac->setControlHint(EffectManifestParameter::ControlHint::KNOB_LINEAR);
fade_time_frac->setSemanticHint(EffectManifestParameter::SemanticHint::UNKNOWN);
fade_time_frac->setUnitsHint(EffectManifestParameter::UnitsHint::UNKNOWN);
fade_time_frac->setMinimum(0);
fade_time_frac->setDefault(0.75);
fade_time_frac->setMaximum(1.0);

EffectManifestParameterPointer cut_off_frac = pManifest->addParameter();
cut_off_frac->setId("cutoff");
cut_off_frac->setName(QObject::tr("Cutoff Time Fraction"));
cut_off_frac->setDescription(QObject::tr("Fraction of time of cutoff."));
cut_off_frac->setControlHint(EffectManifestParameter::ControlHint::KNOB_LINEAR);
cut_off_frac->setSemanticHint(EffectManifestParameter::SemanticHint::UNKNOWN);
cut_off_frac->setUnitsHint(EffectManifestParameter::UnitsHint::UNKNOWN);
cut_off_frac->setMinimum(0);
cut_off_frac->setDefault(0.4);
cut_off_frac->setMaximum(0.9);

// This effect can take away a lot of power and we cannot compensate for that (1.0 is max), so send everything by default.
EffectManifestParameterPointer send = pManifest->addParameter();
send->setId("send_amount");
send->setName(QObject::tr("Send"));
send->setShortName(QObject::tr("Send"));
send->setDescription(QObject::tr(
"How much of the signal to send to the output."));
send->setControlHint(EffectManifestParameter::ControlHint::KNOB_LINEAR);
send->setSemanticHint(EffectManifestParameter::SemanticHint::UNKNOWN);
send->setUnitsHint(EffectManifestParameter::UnitsHint::UNKNOWN);
send->setDefaultLinkType(EffectManifestParameter::LinkType::LINKED);
send->setMinimum(0.0);
send->setDefault(db2ratio(0.0));
send->setMaximum(1.0);

EffectManifestParameterPointer quantize = pManifest->addParameter();
quantize->setId("quantize");
quantize->setName(QObject::tr("Quantize"));
quantize->setShortName(QObject::tr("Quantize"));
quantize->setDescription(QObject::tr(
"Round inverse period to nearest integer per second."));
quantize->setControlHint(EffectManifestParameter::ControlHint::TOGGLE_STEPPING);
quantize->setSemanticHint(EffectManifestParameter::SemanticHint::UNKNOWN);
quantize->setUnitsHint(EffectManifestParameter::UnitsHint::UNKNOWN);
quantize->setDefault(1);
quantize->setMinimum(0);
quantize->setMaximum(1);

return pManifest;
}

TransEffect::TransEffect(EngineEffect* pEffect)
: m_pPeriodParameter(pEffect->getParameterById("period")),
m_pFadeTimeParameter(pEffect->getParameterById("fadetime")),
m_pCutoffTimeParameter(pEffect->getParameterById("cutoff")),
m_pSendParameter(pEffect->getParameterById("send_amount")),
m_pQuantizeParameter(pEffect->getParameterById("quantize")) {
}

void TransEffect::processChannel(const ChannelHandle& handle,
TransGroupState* pGroupState,
const CSAMPLE* pInput,
CSAMPLE* pOutput,
const mixxx::EngineParameters& bufferParameters,
const EffectEnableState enableState,
const GroupFeatureState& groupFeatures) {
Q_UNUSED(handle);

using Sample_rate = ::mixxx::audio::SampleRate::value_t;

TransGroupState& gs = *pGroupState;
auto period = m_pPeriodParameter->value();
auto fade_frac = m_pFadeTimeParameter->value();
auto cutoff_frac = m_pCutoffTimeParameter->value();
auto original_send_target = static_cast<CSAMPLE_GAIN>(m_pSendParameter->value());
Sample_rate sampleRate = bufferParameters.sampleRate();

if (m_pQuantizeParameter->toBool() && groupFeatures.has_beat_length_sec) {
auto four_beat_length_sec = groupFeatures.beat_length_sec * 4.0;
auto next_multiple_in_four_beats = std::round(four_beat_length_sec / period);
if (next_multiple_in_four_beats < 1.0) {
next_multiple_in_four_beats = 1.0;
}
period = four_beat_length_sec / next_multiple_in_four_beats;
}

std::uint32_t period_samples = static_cast<std::uint32_t>(double(sampleRate) * period);

auto on_frac = 1.0 - cutoff_frac;
auto on_samples = double(period_samples) * on_frac;
std::uint32_t ramp_samples = static_cast<std::uint32_t>(on_samples * fade_frac * 0.5);
std::uint32_t kill_point = static_cast<std::uint32_t>(on_samples) - ramp_samples;

auto send_target = original_send_target;
if (enableState == EffectEnableState::Disabling) {
send_target = 0.0;
}

auto factor_delta_per_sample = CSAMPLE_GAIN(1.0) / CSAMPLE_GAIN(ramp_samples);

RampingValue<CSAMPLE_GAIN> send(send_target,
pGroupState->lastSend,
bufferParameters.framesPerBuffer());

/*
* ramp-up ramp-down
* |<---->| |<---->|
*
* |x| +- killpoint
* ^ v
* | /--------\ <-send-> /--------\
* | / \ / \
* | / \ / \
* | / \ / \
* | / \ / \
* | / \ / \
* |/ \ / \
* 0 +---------------------------------------------------------------------------> N
* |<--------------------- period ------------------>|
* |<---- cutoff fraction --->|
*/

for (unsigned int i = 0; i < bufferParameters.samplesPerBuffer(); i += bufferParameters.channelCount()) {
if (gs.playedFrames < kill_point) {
if (gs.transFactor < 1.0) {
gs.transFactor = SampleUtil::clampGain(gs.transFactor + factor_delta_per_sample);
}
} else {
if (gs.transFactor > 0.0) {
gs.transFactor = SampleUtil::clampGain(gs.transFactor - factor_delta_per_sample);
} else if (gs.playedFrames >= period_samples) {
gs.playedFrames = 0;
}
}

auto send_factor = send.getNext();
auto full_factor = SampleUtil::clampGain(gs.transFactor * send_factor);
auto j = i;
for (auto channel = 0U; channel < bufferParameters.channelCount(); channel++) {
pOutput[j] = pInput[j] * full_factor;
++j;
}

gs.playedFrames++;
}

gs.lastSend = send_target;
}
55 changes: 55 additions & 0 deletions src/effects/builtin/transeffect.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#pragma once

#include <cstdint>

#include "effects/effectprocessor.h"
#include "engine/effects/engineeffect.h"
#include "engine/effects/engineeffectparameter.h"
#include "engine/engine.h"
#include "util/class.h"
#include "util/defs.h"
#include "util/sample.h"
#include "util/samplebuffer.h"

class TransGroupState : public EffectState {
public:
TransGroupState(const mixxx::EngineParameters& bufferParameters)
: EffectState(bufferParameters) {
}

virtual ~TransGroupState() = default;

CSAMPLE_GAIN transFactor{0.0};
CSAMPLE_GAIN lastSend{0.0};
std::uint32_t playedFrames{0};
};

class TransEffect : public EffectProcessorImpl<TransGroupState> {
public:
TransEffect(EngineEffect* pEffect);
virtual ~TransEffect() = default;

static QString getId();
static EffectManifestPointer getManifest();

void processChannel(const ChannelHandle& handle,
TransGroupState* pGroupState,
const CSAMPLE* pInput,
CSAMPLE* pOutput,
const mixxx::EngineParameters& bufferParameters,
const EffectEnableState enableState,
const GroupFeatureState& groupFeatures);

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

EngineEffectParameter* m_pPeriodParameter;
EngineEffectParameter* m_pFadeTimeParameter;
EngineEffectParameter* m_pCutoffTimeParameter;
EngineEffectParameter* m_pSendParameter;
EngineEffectParameter* m_pQuantizeParameter;

DISALLOW_COPY_AND_ASSIGN(TransEffect);
};

0 comments on commit 3dff55a

Please sign in to comment.