Add frequency shifter
This commit is contained in:
parent
2908a8b56f
commit
995339d2a2
@ -1,3 +1,3 @@
|
||||
# https://geraintluff.github.io/SUPPORT.txt/
|
||||
|
||||
2026-01-01 Geraint Luff <geraint@signalsmith-audio.co.uk>
|
||||
2026-06-01 Geraint Luff <geraint@signalsmith-audio.co.uk>
|
||||
|
||||
@ -31,7 +31,7 @@ struct AnalyserSTFX : public BaseEffect {
|
||||
|
||||
template<class Storage>
|
||||
void state(Storage &storage) {
|
||||
storage.info("[Basics] Analyser", "A Bark-scale spectrum analyser");
|
||||
storage.info("Analyser", "A Bark-scale spectrum analyser");
|
||||
storage.version(0);
|
||||
|
||||
storage.range("barkResolution", barkResolution)
|
||||
|
||||
2
chorus.h
2
chorus.h
@ -44,7 +44,7 @@ struct ChorusSTFX : public BaseEffect {
|
||||
|
||||
template<class Storage>
|
||||
void state(Storage &storage) {
|
||||
storage.info("[Basics] Chorus", "");
|
||||
storage.info("Chorus", "");
|
||||
storage.version(0);
|
||||
|
||||
stfx::units::rangePercent(storage.range("mix", mix)
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#include "signalsmith-basics/analyser.h"
|
||||
#include "signalsmith-basics/chorus.h"
|
||||
#include "signalsmith-basics/crunch.h"
|
||||
#include "signalsmith-basics/freq-shifter.h"
|
||||
#include "signalsmith-basics/limiter.h"
|
||||
#include "signalsmith-basics/reverb.h"
|
||||
|
||||
@ -25,7 +26,7 @@ bool clap_init(const char *path) {
|
||||
}, {
|
||||
CLAP_PLUGIN_FEATURE_ANALYZER,
|
||||
});
|
||||
|
||||
|
||||
plugins.add<signalsmith::basics::ChorusSTFX>({
|
||||
.clap_version = CLAP_VERSION,
|
||||
.id = "uk.co.signalsmith.basics.chorus",
|
||||
@ -54,6 +55,20 @@ bool clap_init(const char *path) {
|
||||
CLAP_PLUGIN_FEATURE_DISTORTION,
|
||||
});
|
||||
|
||||
plugins.add<signalsmith::basics::FreqShifterSTFX>({
|
||||
.clap_version = CLAP_VERSION,
|
||||
.id = "uk.co.signalsmith.basics.freq-shifter",
|
||||
.name = "[Basics] Frequency Shifter",
|
||||
.vendor = "Signalsmith Audio",
|
||||
.url = "",
|
||||
.manual_url = "",
|
||||
.support_url = "",
|
||||
.version = "1.0.0"
|
||||
}, {
|
||||
CLAP_PLUGIN_FEATURE_AUDIO_EFFECT,
|
||||
CLAP_PLUGIN_FEATURE_FREQUENCY_SHIFTER,
|
||||
});
|
||||
|
||||
plugins.add<signalsmith::basics::LimiterSTFX>({
|
||||
.clap_version = CLAP_VERSION,
|
||||
.id = "uk.co.signalsmith.basics.limiter",
|
||||
|
||||
7
crunch.h
7
crunch.h
@ -1,7 +1,6 @@
|
||||
/* Copyright 2022 Signalsmith Audio Ltd. / Geraint Luff
|
||||
See LICENSE.txt and SUPPORT.txt */
|
||||
#ifndef SIGNALSMITH_BASICS_CRUNCH_H
|
||||
#define SIGNALSMITH_BASICS_CRUNCH_H
|
||||
#pragma once
|
||||
|
||||
#include "dsp/rates.h"
|
||||
#include "dsp/filters.h"
|
||||
@ -40,7 +39,7 @@ struct CrunchSTFX : public BaseEffect {
|
||||
|
||||
template<class Storage>
|
||||
void state(Storage &storage) {
|
||||
storage.info("[Basics] Crunch", "A simple distortion/saturation");
|
||||
storage.info("Crunch", "A simple distortion/saturation");
|
||||
int version = storage.version(0);
|
||||
if (version != 0) return;
|
||||
|
||||
@ -178,5 +177,3 @@ private:
|
||||
};
|
||||
|
||||
}} // namespace
|
||||
|
||||
#endif // include guard
|
||||
|
||||
118
freq-shifter.h
Normal file
118
freq-shifter.h
Normal file
@ -0,0 +1,118 @@
|
||||
/* Copyright 2022 Signalsmith Audio Ltd. / Geraint Luff
|
||||
See LICENSE.txt and SUPPORT.txt */
|
||||
#pragma once
|
||||
|
||||
#include "modules/hilbert-iir/hilbert.h"
|
||||
|
||||
#include "stfx/stfx-library.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace signalsmith { namespace basics {
|
||||
|
||||
template<class BaseEffect>
|
||||
class FreqShifterSTFX;
|
||||
|
||||
using FreqShifterFloat = stfx::LibraryEffect<float, FreqShifterSTFX>;
|
||||
using FreqShifterDouble = stfx::LibraryEffect<double, FreqShifterSTFX>;
|
||||
|
||||
template<class BaseEffect>
|
||||
struct FreqShifterSTFX : public BaseEffect {
|
||||
using typename BaseEffect::Sample;
|
||||
using Complex = std::complex<Sample>;
|
||||
using typename BaseEffect::ParamRange;
|
||||
using typename BaseEffect::ParamStepped;
|
||||
|
||||
static double unitToShiftHz(double x) {
|
||||
return 100*x/(1.1 - x*x);
|
||||
}
|
||||
static double shiftHzToUnit(double y) {
|
||||
return (std::sqrt(2500 + 1.1*y*y) - 50)/y;
|
||||
}
|
||||
|
||||
ParamRange mix{1};
|
||||
ParamRange shift{shiftHzToUnit(50)};
|
||||
ParamStepped reflect{0};
|
||||
|
||||
template<class Storage>
|
||||
void state(Storage &storage) {
|
||||
storage.info("Frequency Shifter", "A Hilbert / Bode single-sideband modulator");
|
||||
int version = storage.version(0);
|
||||
if (version != 0) return;
|
||||
|
||||
stfx::units::rangePercent(storage.range("mix", mix)
|
||||
.info("mix", "wet/dry")
|
||||
.range(0, 0.5, 1));
|
||||
storage.range("shift", shift)
|
||||
.info("shift", "pre-distortion input gain")
|
||||
.range(-1, 0, 1)
|
||||
.unit("Hz", 1, shiftHzToUnit, unitToShiftHz, shiftHzToUnit(-9.99), shiftHzToUnit(9.99))
|
||||
.unit("Hz", 0, shiftHzToUnit, unitToShiftHz);
|
||||
storage.stepped("reflect", reflect)
|
||||
.info("reflect", "0Hz reflection mode")
|
||||
.range(0, 3)
|
||||
.label(0, "no reflect/duplicate", "reflect below 0Hz", "duplicate above 0Hz", "always reflect/duplicate");
|
||||
}
|
||||
|
||||
template<class Config>
|
||||
void configureSTFX(Config &config) {
|
||||
auto channels = config.outputChannels = config.inputChannels;
|
||||
config.auxInputs.resize(0);
|
||||
config.auxOutputs.resize(0);
|
||||
|
||||
hilbert = {Sample(config.sampleRate), channels};
|
||||
}
|
||||
|
||||
void reset() {
|
||||
shiftPhaseBefore = shiftPhaseAfter = 0;
|
||||
shiftBefore = shiftAfter = 1;
|
||||
hilbert.reset();
|
||||
}
|
||||
|
||||
|
||||
template <class Io, class Config, class Block>
|
||||
void processSTFX(Io &io, Config &config, Block &block) {
|
||||
auto dry = block.smooth(mix, [](double m){return 1 - m*m;});
|
||||
auto wet = block.smooth(mix, [](double m){return m*(2 - m);});
|
||||
|
||||
auto phaseStep = block.smooth(shift, [&](double u){
|
||||
auto hz = unitToShiftHz(u);
|
||||
return hz/config.sampleRate;
|
||||
});
|
||||
|
||||
int mode = reflect;
|
||||
bool noReflectDown = (mode == 0 || mode == 2);
|
||||
bool duplicateUp = (mode == 2 || mode == 3);
|
||||
|
||||
for (size_t i = 0; i < block.length; ++i) {
|
||||
auto gainDry = dry.at(i), gainWet = wet.at(i);
|
||||
for (size_t c = 0; c < config.inputChannels; ++c) {
|
||||
Sample x = io.input[c][i];
|
||||
// In general, this may alias at the high end when shifting up
|
||||
// but our Hilbert has a 20kHz lowpass, so that's enough
|
||||
// room for our maximum +1000Hz shift
|
||||
Complex y = shiftAfter*hilbert(x*shiftBefore, c);
|
||||
io.output[c][i] = gainDry*x + gainWet*y.real();
|
||||
}
|
||||
|
||||
auto ps = phaseStep.at(i);
|
||||
bool shiftInput = (ps < 0) ? noReflectDown : duplicateUp;
|
||||
if (shiftInput) {
|
||||
shiftPhaseBefore += ps;
|
||||
shiftBefore = std::polar(Sample(1), shiftPhaseBefore*Sample(2*M_PI));
|
||||
} else {
|
||||
shiftPhaseAfter += ps;
|
||||
shiftAfter = std::polar(Sample(1), shiftPhaseAfter*Sample(2*M_PI));
|
||||
}
|
||||
}
|
||||
shiftPhaseBefore -= std::floor(shiftPhaseBefore);
|
||||
shiftPhaseAfter -= std::floor(shiftPhaseAfter);
|
||||
}
|
||||
|
||||
private:
|
||||
Sample shiftPhaseBefore = 0, shiftPhaseAfter = 0;
|
||||
Complex shiftBefore = 1, shiftAfter = 1;
|
||||
signalsmith::hilbert::HilbertIIR<Sample> hilbert;
|
||||
};
|
||||
|
||||
}} // namespace
|
||||
1
include/signalsmith-basics/freq-shifter.h
Normal file
1
include/signalsmith-basics/freq-shifter.h
Normal file
@ -0,0 +1 @@
|
||||
#include "../../freq-shifter.h"
|
||||
@ -1,7 +1,6 @@
|
||||
/* Copyright 2022 Signalsmith Audio Ltd. / Geraint Luff
|
||||
Released under the Boost Software License (see LICENSE.txt) */
|
||||
#ifndef SIGNALSMITH_BASICS_LIMITER_H
|
||||
#define SIGNALSMITH_BASICS_LIMITER_H
|
||||
#pragma once
|
||||
|
||||
#include "dsp/delay.h"
|
||||
#include "dsp/envelopes.h"
|
||||
@ -36,7 +35,7 @@ struct LimiterSTFX : public BaseEffect {
|
||||
|
||||
template<class Storage>
|
||||
void state(Storage &storage) {
|
||||
storage.info("[Basics] Limiter", "A simple lookahead limiter");
|
||||
storage.info("Limiter", "A simple lookahead limiter");
|
||||
int version = storage.version(4);
|
||||
if (version != 4) return;
|
||||
|
||||
@ -202,5 +201,3 @@ private:
|
||||
};
|
||||
|
||||
}} // namespace
|
||||
|
||||
#endif // include guard
|
||||
|
||||
6
reverb.h
6
reverb.h
@ -1,7 +1,6 @@
|
||||
/* Copyright 2022 Signalsmith Audio Ltd. / Geraint Luff
|
||||
Released under the Boost Software License (see LICENSE.txt) */
|
||||
#ifndef SIGNALSMITH_BASICS_REVERB_H
|
||||
#define SIGNALSMITH_BASICS_REVERB_H
|
||||
#pragma once
|
||||
|
||||
#include "dsp/delay.h"
|
||||
#include "dsp/mix.h"
|
||||
@ -43,7 +42,7 @@ struct ReverbSTFX : public BaseEffect {
|
||||
template<class Storage>
|
||||
void state(Storage &storage) {
|
||||
|
||||
storage.info("[Basics] Reverb", "An FDN reverb");
|
||||
storage.info("Reverb", "An FDN reverb");
|
||||
int version = storage.version(5);
|
||||
if (version != 5) return;
|
||||
|
||||
@ -408,4 +407,3 @@ private:
|
||||
};
|
||||
|
||||
}} // namespace
|
||||
#endif // include guard
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user