From 4782f1ff322d66c825e035bc2ced8aa7643de8fc Mon Sep 17 00:00:00 2001 From: Geraint Date: Sun, 6 Jul 2025 09:15:00 +0100 Subject: [PATCH] Chorus pans shared delay taps instead of per-channel offset --- chorus.h | 68 +++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 48 insertions(+), 20 deletions(-) diff --git a/chorus.h b/chorus.h index 02a496e..5b9bca9 100644 --- a/chorus.h +++ b/chorus.h @@ -40,6 +40,7 @@ struct ChorusSTFX : public BaseEffect { ParamRange mix{0.6}; ParamRange depthMs{15}; ParamRange detune{5}; + ParamRange stereo{1}; template void state(Storage &storage) { @@ -59,6 +60,10 @@ struct ChorusSTFX : public BaseEffect { .info("detune", "detuning depth") .range(1, 8, 50) .unit(" s.t.", 0); + + stfx::units::rangePercent(storage.range("stereo", stereo) + .info("stereo", "") + .range(0, 0.5, 1.5)); } template @@ -74,12 +79,16 @@ struct ChorusSTFX : public BaseEffect { void reset() { delay.reset(); phase = 0; + stereoPhase = 0; } template void processSTFX(Io &io, Config &config, Block &block) { Sample detuneHz = detune*0.45f/depthMs; // 0.45ms oscillation at 1Hz is about 1 semitone 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(); 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 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 c = 0; c < 6; ++c) { @@ -98,34 +109,50 @@ struct ChorusSTFX : public BaseEffect { } delay.write(multiIn); - for (size_t oc = 0; oc < config.outputChannels; ++oc) { - Complex phaseComplex = std::polar(Sample(1), phase*Sample(2*M_PI) + oc*Sample(2.632)); + Complex phaseComplex = std::polar(Sample(1), phase*Sample(2*M_PI)); + 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 multiOutFrom; for (size_t c = 0; c < 6; ++c) { 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); - - if (fading) { - // read a second set of delay times, and fade between them - std::array 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]); - } + 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 -= std::floor(phase); + stereoPhase -= std::floor(stereoPhase); } template @@ -136,6 +163,7 @@ private: signalsmith::delay::MultiDelay delay; Sample phase = 0; + Sample stereoPhase = 0; }; }} // namespace