1
0

Chorus pans shared delay taps instead of per-channel offset

This commit is contained in:
Geraint 2025-07-06 09:15:00 +01:00
parent 2cf6beaf9d
commit 4782f1ff32

View File

@ -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,34 +109,50 @@ 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) {
Sample osc = (phaseComplex*oscillatorOffsets[c]).real();
delaySamples[c] = depthSamples*Sample(0.5)*(1 + osc);
}
delay.readMulti(delaySamples, multiOut);
if (fading) {
// read a second set of delay times, and fade between them
std::array<Sample, 6> multiOutFrom;
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] = depthSamplesFrom*Sample(0.5)*(1 + osc);
} }
delay.readMulti(delaySamples, multiOut); delay.readMulti(delaySamples, multiOutFrom);
for (size_t c = 0; c < 6; ++c) {
if (fading) { multiOut[c] = block.fade(i, multiOutFrom[c], multiOut[c]);
// read a second set of delay times, and fade between them
std::array<Sample, 6> multiOutFrom;
for (size_t c = 0; c < 6; ++c) {
Sample osc = (phaseComplex*oscillatorOffsets[c]).real();
delaySamples[c] = depthSamplesFrom*Sample(0.5)*(1 + osc);
}
delay.readMulti(delaySamples, multiOutFrom);
for (size_t c = 0; c < 6; ++c) {
multiOut[c] = block.fade(i, multiOutFrom[c], multiOut[c]);
}
} }
// Arbitrarily chosen gains, 4 positive, 2 negative
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);
} }
// Arbitrarily chosen gains, 4 positive, 2 negative
Sample sum = multiOut[0] - multiOut[1] + multiOut[2] + multiOut[3] - multiOut[4] + multiOut[5];
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