Chorus pans shared delay taps instead of per-channel offset
This commit is contained in:
parent
2cf6beaf9d
commit
4782f1ff32
34
chorus.h
34
chorus.h
@ -40,6 +40,7 @@ struct ChorusSTFX : public BaseEffect {
|
|||||||
ParamRange mix{0.6};
|
ParamRange mix{0.6};
|
||||||
ParamRange depthMs{15};
|
ParamRange depthMs{15};
|
||||||
ParamRange detune{5};
|
ParamRange detune{5};
|
||||||
|
ParamRange stereo{1};
|
||||||
|
|
||||||
template<class Storage>
|
template<class Storage>
|
||||||
void state(Storage &storage) {
|
void state(Storage &storage) {
|
||||||
@ -59,6 +60,10 @@ struct ChorusSTFX : public BaseEffect {
|
|||||||
.info("detune", "detuning depth")
|
.info("detune", "detuning depth")
|
||||||
.range(1, 8, 50)
|
.range(1, 8, 50)
|
||||||
.unit(" s.t.", 0);
|
.unit(" s.t.", 0);
|
||||||
|
|
||||||
|
stfx::units::rangePercent(storage.range("stereo", stereo)
|
||||||
|
.info("stereo", "")
|
||||||
|
.range(0, 0.5, 1.5));
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class Config>
|
template<class Config>
|
||||||
@ -74,12 +79,16 @@ struct ChorusSTFX : public BaseEffect {
|
|||||||
void reset() {
|
void reset() {
|
||||||
delay.reset();
|
delay.reset();
|
||||||
phase = 0;
|
phase = 0;
|
||||||
|
stereoPhase = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class Io, class Config, class Block>
|
template<class Io, class Config, class Block>
|
||||||
void processSTFX(Io &io, Config &config, Block &block) {
|
void processSTFX(Io &io, Config &config, Block &block) {
|
||||||
Sample detuneHz = detune*0.45f/depthMs; // 0.45ms oscillation at 1Hz is about 1 semitone
|
Sample detuneHz = detune*0.45f/depthMs; // 0.45ms oscillation at 1Hz is about 1 semitone
|
||||||
Sample phaseStep = detuneHz/config.sampleRate;
|
Sample phaseStep = detuneHz/config.sampleRate;
|
||||||
|
Sample stereoPhaseStep = phaseStep/Sample(M_PI);
|
||||||
|
Complex stereoComplexPerOutput = std::polar(Sample(1), Sample(2*M_PI)/config.outputChannels);
|
||||||
|
Complex stereoComplexPerInternal = std::polar(Sample(1), Sample(1));
|
||||||
|
|
||||||
bool fading = depthMs.from() != depthMs.to();
|
bool fading = depthMs.from() != depthMs.to();
|
||||||
Sample depthSamples = depthMs.to()*0.001*config.sampleRate;
|
Sample depthSamples = depthMs.to()*0.001*config.sampleRate;
|
||||||
@ -90,6 +99,8 @@ struct ChorusSTFX : public BaseEffect {
|
|||||||
|
|
||||||
auto dry = block.smooth(mix, [](double m){return 1 - m*m;});
|
auto dry = block.smooth(mix, [](double m){return 1 - m*m;});
|
||||||
auto wet = block.smooth(mix, [](double m){return m*(2 - m)/std::sqrt(Sample(6));});
|
auto wet = block.smooth(mix, [](double m){return m*(2 - m)/std::sqrt(Sample(6));});
|
||||||
|
bool notMono = (config.outputChannels > 1);
|
||||||
|
auto width = block.smooth(stereo);
|
||||||
|
|
||||||
for (size_t i = 0; i < block.length; ++i) {
|
for (size_t i = 0; i < block.length; ++i) {
|
||||||
for (size_t c = 0; c < 6; ++c) {
|
for (size_t c = 0; c < 6; ++c) {
|
||||||
@ -98,8 +109,7 @@ struct ChorusSTFX : public BaseEffect {
|
|||||||
}
|
}
|
||||||
delay.write(multiIn);
|
delay.write(multiIn);
|
||||||
|
|
||||||
for (size_t oc = 0; oc < config.outputChannels; ++oc) {
|
Complex phaseComplex = std::polar(Sample(1), phase*Sample(2*M_PI));
|
||||||
Complex phaseComplex = std::polar(Sample(1), phase*Sample(2*M_PI) + oc*Sample(2.632));
|
|
||||||
for (size_t c = 0; c < 6; ++c) {
|
for (size_t c = 0; c < 6; ++c) {
|
||||||
Sample osc = (phaseComplex*oscillatorOffsets[c]).real();
|
Sample osc = (phaseComplex*oscillatorOffsets[c]).real();
|
||||||
delaySamples[c] = depthSamples*Sample(0.5)*(1 + osc);
|
delaySamples[c] = depthSamples*Sample(0.5)*(1 + osc);
|
||||||
@ -121,11 +131,28 @@ struct ChorusSTFX : public BaseEffect {
|
|||||||
|
|
||||||
// Arbitrarily chosen gains, 4 positive, 2 negative
|
// Arbitrarily chosen gains, 4 positive, 2 negative
|
||||||
Sample sum = multiOut[0] - multiOut[1] + multiOut[2] + multiOut[3] - multiOut[4] + multiOut[5];
|
Sample sum = multiOut[0] - multiOut[1] + multiOut[2] + multiOut[3] - multiOut[4] + multiOut[5];
|
||||||
io.output[oc][i] = multiIn[oc]*dry.at(i) + sum*wet.at(i);
|
if (notMono) {
|
||||||
|
Complex stereoComplex = std::polar(Sample(width.at(i)), stereoPhase*Sample(2*M_PI));
|
||||||
|
for (size_t oc = 0; oc < config.outputChannels; ++oc) {
|
||||||
|
Sample sumC = sum;
|
||||||
|
Complex rot = stereoComplex;
|
||||||
|
for (size_t i = 0; i < 6; ++i) {
|
||||||
|
sumC += rot.real()*multiOut[i];
|
||||||
|
rot *= stereoComplexPerInternal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
io.output[oc][i] = multiIn[oc]*dry.at(i) + sumC*wet.at(i);
|
||||||
|
stereoComplex *= stereoComplexPerOutput;
|
||||||
|
}
|
||||||
|
stereoPhase += stereoPhaseStep;
|
||||||
|
} else {
|
||||||
|
io.output[0][i] = multiIn[0]*dry.at(i) + sum*wet.at(i);
|
||||||
|
}
|
||||||
|
|
||||||
phase += phaseStep;
|
phase += phaseStep;
|
||||||
}
|
}
|
||||||
phase -= std::floor(phase);
|
phase -= std::floor(phase);
|
||||||
|
stereoPhase -= std::floor(stereoPhase);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class Storage>
|
template<class Storage>
|
||||||
@ -136,6 +163,7 @@ private:
|
|||||||
|
|
||||||
signalsmith::delay::MultiDelay<Sample, signalsmith::delay::InterpolatorLagrange7> delay;
|
signalsmith::delay::MultiDelay<Sample, signalsmith::delay::InterpolatorLagrange7> delay;
|
||||||
Sample phase = 0;
|
Sample phase = 0;
|
||||||
|
Sample stereoPhase = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
}} // namespace
|
}} // namespace
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user