mirror of
https://codeberg.org/armin/rmx-19.git
synced 2026-10-11 16:51:55 +02:00
add generative evolution, cross-mod and reverb FX; expand preset bank
- 4 new params (xmod/xmix/evolve/warp) with free-running brownian evolution - evolution: tune drift, osc2 detune sway, osc-mix crossfade, filter cutoff wander, resonance growl, warp drive/bias, cross-mod bloom - per-voice unison detune spread for fat stacks - FX engine: 3-tap ensemble chorus, filtered-feedback ping-pong delay, Schroeder hall reverb; new FX types 4-7 (REV HALL/ENS+REV/DLY+REV/CATHEDRAL) - 195 presets: 40-field realignment, genre-tuned FX identities, showcase eval - calibrate banks vs attack-inclusive peak (median 0.263); fix square-LFO on filter sustain collapse
This commit is contained in:
parent
bc417e1cc1
commit
35d6cd014a
8 changed files with 2016 additions and 601 deletions
|
|
@ -14,7 +14,8 @@ static inline void dbgNote (const char* ev, int note, float vel)
|
|||
}
|
||||
|
||||
class RMX19AudioProcessor::FxEngine
|
||||
{public:
|
||||
{
|
||||
public:
|
||||
void prepare (double sampleRate, int maxSamples)
|
||||
{
|
||||
sr = sampleRate;
|
||||
|
|
@ -28,6 +29,9 @@ class RMX19AudioProcessor::FxEngine
|
|||
chorusR.setMaximumDelayInSamples (maxDelay);
|
||||
delL.setMaximumDelayInSamples (maxDelay);
|
||||
delR.setMaximumDelayInSamples (maxDelay);
|
||||
|
||||
rebuildCombBuffers (true);
|
||||
rebuildCombBuffers (false);
|
||||
}
|
||||
|
||||
void reset()
|
||||
|
|
@ -35,6 +39,11 @@ class RMX19AudioProcessor::FxEngine
|
|||
chorusL.reset(); chorusR.reset();
|
||||
delL.reset(); delR.reset();
|
||||
chPh = 0.0;
|
||||
lpL = lpR = 0.0f;
|
||||
for (auto& c : combL) c.reset();
|
||||
for (auto& c : combR) c.reset();
|
||||
for (auto& a : apL) a.reset();
|
||||
for (auto& a : apR) a.reset();
|
||||
}
|
||||
|
||||
void process (const VParams& p, float* L, float* R, int num, int syncIdx, double bpm)
|
||||
|
|
@ -43,53 +52,137 @@ class RMX19AudioProcessor::FxEngine
|
|||
if (type == 0) return;
|
||||
|
||||
const float wet = p.fxAmt;
|
||||
const float dry = 1.0f - wet * 0.5f;
|
||||
const float chBase = 0.008f * (float) sr; // 8 ms chorus base
|
||||
const float chDepth = 0.0035f * (float) sr; // +-3.5 ms wobble
|
||||
const float dryCh = 1.0f - wet * 0.5f;
|
||||
const float chBase = 0.009f * (float) sr; // 9 ms ensemble base
|
||||
const float chDepth = 0.006f * (float) sr; // +-6 ms wobble
|
||||
float delSamples = (float) (p.fxTime * sr);
|
||||
if (syncIdx > 0 && bpm > 1.0)
|
||||
delSamples = (float) ((60.0 / bpm) * prm::delaySyncBeats (syncIdx) * sr);
|
||||
const float feedback = 0.40f;
|
||||
const float fb = 0.20f + 0.35f * wet;
|
||||
const float ff = 0.18f; // darkening of repeats
|
||||
|
||||
const bool doChorus = (type == 1 || type == 3 || type == 5 || type == 7);
|
||||
const bool doDelay = (type == 2 || type == 3 || type == 6 || type == 7);
|
||||
const bool doVerb = (type >= 4);
|
||||
|
||||
for (int s = 0; s < num; ++s)
|
||||
{
|
||||
const float inL = L[s], inR = R[s];
|
||||
float outL = inL * dry, outR = inR * dry;
|
||||
float outL = inL * dryCh, outR = inR * dryCh;
|
||||
|
||||
if (type == 1 || type == 3)
|
||||
if (doChorus)
|
||||
{
|
||||
const double rate = type == 3 ? 0.55 : 0.38;
|
||||
chPh += rate / sr;
|
||||
if (chPh >= 1.0) chPh -= 1.0;
|
||||
const float m = 0.5f + 0.5f * (float) std::sin (chPh * 2.0 * juce::MathConstants<double>::pi);
|
||||
// 3-voice ensemble with a touch of internal regeneration
|
||||
const double w = chPh * 2.0 * juce::MathConstants<double>::pi;
|
||||
const float t1 = (float) std::sin (w);
|
||||
const float t2 = (float) std::sin (w + 2.09439f);
|
||||
const float t3 = (float) std::sin (w + 4.18879f);
|
||||
chPh += 0.34 / sr; if (chPh >= 1.0) chPh -= 1.0;
|
||||
|
||||
chorusL.pushSample (0, inL);
|
||||
chorusR.pushSample (0, inR);
|
||||
const float mL = chorusL.popSample (0, chBase + chDepth * m);
|
||||
const float mR = chorusR.popSample (0, chBase + chDepth * (1.0f - m));
|
||||
outL += mL * wet * 0.7f;
|
||||
outR += mR * wet * 0.7f;
|
||||
const float mL = 0.5f * (chorusL.popSample (0, chBase + chDepth * t1)
|
||||
+ chorusL.popSample (0, chBase + chDepth * t2));
|
||||
const float mR = 0.5f * (chorusR.popSample (0, chBase + chDepth * t2)
|
||||
+ chorusR.popSample (0, chBase + chDepth * t3));
|
||||
chorusL.pushSample (0, mL * 0.30f);
|
||||
chorusR.pushSample (0, mR * 0.30f);
|
||||
outL += mL * wet * 0.8f;
|
||||
outR += mR * wet * 0.8f;
|
||||
}
|
||||
|
||||
if (type == 2 || type == 3)
|
||||
if (doDelay)
|
||||
{
|
||||
const float rL = delL.popSample (0, delSamples);
|
||||
const float rR = delR.popSample (0, delSamples);
|
||||
delL.pushSample (0, inL + rR * feedback);
|
||||
delR.pushSample (0, inR + rL * feedback);
|
||||
lpL += ff * (rL - lpL); lpR += ff * (rR - lpR);
|
||||
delL.pushSample (0, inL + lpR * fb);
|
||||
delR.pushSample (0, inR + lpL * fb);
|
||||
outL += rL * wet;
|
||||
outR += rR * wet;
|
||||
}
|
||||
|
||||
if (doVerb)
|
||||
{
|
||||
float revL = 0.0f, revR = 0.0f;
|
||||
for (int i = 0; i < 6; ++i)
|
||||
{
|
||||
float o = combL[i].process (inL * 0.25f, 0.84f, 0.30f);
|
||||
revL += o * 0.22f;
|
||||
o = combR[i].process (inR * 0.25f, 0.84f, 0.30f);
|
||||
revR += o * 0.22f;
|
||||
}
|
||||
apL[0].process (revL, 0.70f); apL[1].process (revL, 0.57f);
|
||||
apR[0].process (revR, 0.70f); apR[1].process (revR, 0.57f);
|
||||
const float revWet = (type == 7 ? wet : wet * 0.8f);
|
||||
outL += revL * revWet;
|
||||
outR += revR * revWet;
|
||||
}
|
||||
|
||||
L[s] = outL;
|
||||
R[s] = outR;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
struct Comb
|
||||
{
|
||||
void prepare (double s, double seconds)
|
||||
{
|
||||
size = (int) (s * seconds);
|
||||
buf.assign (size, 0.0f);
|
||||
idx = 0; out = 0.0f; lpIn = 0.0f; lpOut = 0.0f;
|
||||
}
|
||||
void reset() { std::fill (buf.begin(), buf.end(), 0.0f); idx = 0; out = 0.0f; lpIn = lpOut = 0.0f; }
|
||||
float process (float in, float fb, float damp)
|
||||
{
|
||||
out = buf[idx];
|
||||
lpOut = out * (1.0f - damp) + lpIn * damp;
|
||||
lpIn = out;
|
||||
buf[idx] = in + lpOut * fb;
|
||||
if (++idx >= size) idx = 0;
|
||||
return out;
|
||||
}
|
||||
std::vector<float> buf;
|
||||
int size = 0, idx = 0;
|
||||
float out = 0.0f, lpIn = 0.0f, lpOut = 0.0f;
|
||||
};
|
||||
struct Allpass
|
||||
{
|
||||
void prepare (double s, double seconds)
|
||||
{
|
||||
size = (int) (s * seconds);
|
||||
buf.assign (size, 0.0f);
|
||||
idx = 0;
|
||||
}
|
||||
void reset() { std::fill (buf.begin(), buf.end(), 0.0f); idx = 0; }
|
||||
void process (float& x, float fb)
|
||||
{
|
||||
const float w = buf[idx];
|
||||
buf[idx] = x + w * fb;
|
||||
x = w - fb * buf[idx];
|
||||
if (++idx >= size) idx = 0;
|
||||
}
|
||||
std::vector<float> buf;
|
||||
int size = 0, idx = 0;
|
||||
};
|
||||
|
||||
void rebuildCombBuffers (bool left)
|
||||
{
|
||||
const double combs[6] = { 0.0297, 0.0371, 0.0411, 0.0437, 0.0311, 0.0290 };
|
||||
const double off = left ? 0.0 : 0.008;
|
||||
for (int i = 0; i < 6; ++i)
|
||||
(left ? combL[i] : combR[i]).prepare (sr, combs[i] + off);
|
||||
(left ? apL[0] : apR[0]).prepare (sr, 0.0050 + (left ? 0.0 : 0.0011));
|
||||
(left ? apL[1] : apR[1]).prepare (sr, 0.0161 + (left ? 0.0 : 0.0023));
|
||||
}
|
||||
|
||||
juce::dsp::DelayLine<float> chorusL, chorusR, delL, delR;
|
||||
std::array<Comb, 6> combL, combR;
|
||||
std::array<Allpass, 2> apL, apR;
|
||||
double sr = 48000.0;
|
||||
double chPh = 0.0;
|
||||
float lpL = 0.0f, lpR = 0.0f;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue