mirror of
https://codeberg.org/armin/monostep.git
synced 2026-09-01 04:10:46 +02:00
Add delay/reverb dry-wet, granular randomize toggles, rework accent DSP
- Add delay and reverb Dry/Wet parameters and knobs, wired into presets - Add Accent/Slide/Fine/Rate toggles to the Randomize panel with new per-aspect pattern randomizers - Soften accent: modest volume lift plus small cutoff and filter-attack modulation instead of the heavy 5x gain boost - Halve reverb comb/allpass delays so the wet speaks sooner - Regenerate a fresh build stamp on every build
This commit is contained in:
parent
342d413563
commit
662a1f7383
11 changed files with 310 additions and 97 deletions
|
|
@ -7,17 +7,19 @@ namespace monostep
|
|||
{
|
||||
|
||||
// Freeverb-style comb / allpass sizes, slightly offset per channel so the
|
||||
// left and right reverb tails decorrelate.
|
||||
// left and right reverb tails decorrelate. Halved from the classic freeverb
|
||||
// set so the early reflections land ~13 ms after the note instead of ~25 ms:
|
||||
// a 100%-wet reverb keeps its tail but speaks much sooner.
|
||||
static constexpr int combSizes[2][4] =
|
||||
{
|
||||
{ 1116, 1188, 1277, 1356 },
|
||||
{ 1126, 1198, 1287, 1366 }
|
||||
{ 558, 594, 639, 678 },
|
||||
{ 563, 599, 644, 683 }
|
||||
};
|
||||
|
||||
static constexpr int allpassSizes[2][2] =
|
||||
{
|
||||
{ 225, 556 },
|
||||
{ 235, 566 }
|
||||
{ 113, 278 },
|
||||
{ 118, 283 }
|
||||
};
|
||||
|
||||
float FxProcessor::delayTimeSeconds (float knob, bool sync, double bpm)
|
||||
|
|
@ -36,7 +38,7 @@ juce::String FxProcessor::delayTimeLabel (float knob, bool sync, double bpm)
|
|||
{
|
||||
if (sync)
|
||||
{
|
||||
static const char* names[] = { "1/16", "1/8", "1/8D", "1/4", "1/4D", "1/2", "1/2D", "1W" };
|
||||
static const char* names[] = { "1/16", "1/8", "3/16", "1/4", "3/8", "1/2", "3/4", "1 bar" };
|
||||
const int idx = juce::roundToInt (juce::jlimit (0.0f, 1.0f, knob) * 7.0f);
|
||||
return names[juce::jlimit (0, 7, idx)];
|
||||
}
|
||||
|
|
@ -112,13 +114,15 @@ void FxProcessor::reset()
|
|||
|
||||
void FxProcessor::setParams (const Params& p)
|
||||
{
|
||||
params.delayTime = juce::jlimit (0.0f, 1.0f, p.delayTime);
|
||||
params.delayMix = juce::jlimit (0.0f, 1.0f, p.delayMix);
|
||||
params.delaySync = p.delaySync;
|
||||
params.reverbRoom = juce::jlimit (0.0f, 1.0f, p.reverbRoom);
|
||||
params.reverbMix = juce::jlimit (0.0f, 1.0f, p.reverbMix);
|
||||
params.reverbDiff = juce::jlimit (0.0f, 1.0f, p.reverbDiff);
|
||||
params.bpm = juce::jmax (20.0, p.bpm);
|
||||
params.delayTime = juce::jlimit (0.0f, 1.0f, p.delayTime);
|
||||
params.delayFeedback = juce::jlimit (0.0f, 0.9f, p.delayFeedback);
|
||||
params.delaySync = p.delaySync;
|
||||
params.delayMix = juce::jlimit (0.0f, 1.0f, p.delayMix);
|
||||
params.reverbRoom = juce::jlimit (0.0f, 1.0f, p.reverbRoom);
|
||||
params.reverbLevel = juce::jlimit (0.0f, 1.0f, p.reverbLevel);
|
||||
params.reverbMix = juce::jlimit (0.0f, 1.0f, p.reverbMix);
|
||||
params.reverbDiff = juce::jlimit (0.0f, 1.0f, p.reverbDiff);
|
||||
params.bpm = juce::jmax (20.0, p.bpm);
|
||||
|
||||
currentDelaySeconds = delayTimeSeconds (params.delayTime, params.delaySync, params.bpm);
|
||||
}
|
||||
|
|
@ -128,8 +132,23 @@ void FxProcessor::process (juce::AudioBuffer<float>& buffer, int numSamples)
|
|||
if (numSamples <= 0 || buffer.getNumChannels() == 0)
|
||||
return;
|
||||
|
||||
// The reverb must feed off the dry (pre-delay) signal, not the delay's
|
||||
// output, so snapshot the incoming buffer before the delay runs.
|
||||
const bool reverbActive = params.reverbMix > 0.0001f && params.reverbLevel > 0.0001f;
|
||||
if (reverbActive)
|
||||
{
|
||||
if (dryScratch.getNumSamples() < numSamples)
|
||||
dryScratch.setSize (buffer.getNumChannels(), numSamples, false, false, true);
|
||||
|
||||
const int n = juce::jmin (buffer.getNumChannels(), dryScratch.getNumChannels());
|
||||
for (int c = 0; c < n; ++c)
|
||||
dryScratch.copyFrom (c, 0, buffer, c, 0, numSamples);
|
||||
}
|
||||
|
||||
processDelay (buffer, numSamples);
|
||||
processReverb (buffer, numSamples);
|
||||
|
||||
if (reverbActive)
|
||||
processReverb (dryScratch, buffer, numSamples);
|
||||
}
|
||||
|
||||
void FxProcessor::processDelay (juce::AudioBuffer<float>& buffer, int numSamples)
|
||||
|
|
@ -166,7 +185,7 @@ void FxProcessor::processDelay (juce::AudioBuffer<float>& buffer, int numSamples
|
|||
|
||||
// Feedback with a little high-frequency damping.
|
||||
d.feedbackLp += 0.35f * (delayed - d.feedbackLp);
|
||||
d.memory[d.writeIndex] = x + 0.42f * d.feedbackLp;
|
||||
d.memory[d.writeIndex] = x + params.delayFeedback * d.feedbackLp;
|
||||
|
||||
out[i] = x + delayed * mix;
|
||||
|
||||
|
|
@ -176,24 +195,38 @@ void FxProcessor::processDelay (juce::AudioBuffer<float>& buffer, int numSamples
|
|||
}
|
||||
}
|
||||
|
||||
void FxProcessor::processReverb (juce::AudioBuffer<float>& buffer, int numSamples)
|
||||
void FxProcessor::processReverb (const juce::AudioBuffer<float>& dryIn,
|
||||
juce::AudioBuffer<float>& buffer, int numSamples)
|
||||
{
|
||||
if (params.reverbMix <= 0.0001f)
|
||||
if (params.reverbMix <= 0.0001f || params.reverbLevel <= 0.0001f)
|
||||
return;
|
||||
|
||||
const int n = juce::jmin (numChannels, buffer.getNumChannels());
|
||||
const int n = juce::jmin (numChannels, juce::jmin (dryIn.getNumChannels(), buffer.getNumChannels()));
|
||||
const float combGain = 0.70f + 0.28f * params.reverbRoom; // 0.70 .. 0.98
|
||||
const float allpassGain = 0.75f * params.reverbDiff; // 0 .. 0.75
|
||||
// Room normalisation: the comb network's DC gain is ~4 / (1 - combGain),
|
||||
// so scaling by (1 - combGain) keeps the wet level bounded as the room
|
||||
// (decay length) grows instead of blowing up near oscillation. The 2.5
|
||||
// constant is calibrated (standalone replication of this network): at
|
||||
// room 0.5 it puts the note-off ring ~ +6 dB above the dry and the tail
|
||||
// ~ -6 dB one second in, so the reverb is clearly audible even at modest
|
||||
// dry/wet settings. The wet is intentionally NOT clipped here: any
|
||||
// soft-clip knee far below the signal level pins the wet at a constant
|
||||
// flat value (the early tail stops audibly decaying, which reads as a
|
||||
// "reverb slowly kicking in" plateau) and makes 100% wet sound quiet
|
||||
// next to a hot dry signal.
|
||||
const float wetScale = 2.5f * (1.0f - combGain);
|
||||
const float mix = params.reverbMix;
|
||||
|
||||
for (int ch = 0; ch < n; ++ch)
|
||||
{
|
||||
auto& r = reverbs[ch];
|
||||
const float* in = dryIn.getReadPointer (ch);
|
||||
auto* out = buffer.getWritePointer (ch);
|
||||
|
||||
for (int i = 0; i < numSamples; ++i)
|
||||
{
|
||||
const float x = out[i];
|
||||
const float x = in[i];
|
||||
|
||||
float combSum = 0.0f;
|
||||
for (int c = 0; c < 4; ++c)
|
||||
|
|
@ -208,7 +241,7 @@ void FxProcessor::processReverb (juce::AudioBuffer<float>& buffer, int numSample
|
|||
pos = 0;
|
||||
}
|
||||
|
||||
float wet = combSum * 0.012f;
|
||||
float wet = combSum * wetScale * params.reverbLevel;
|
||||
|
||||
for (int a = 0; a < 2; ++a)
|
||||
{
|
||||
|
|
@ -223,7 +256,7 @@ void FxProcessor::processReverb (juce::AudioBuffer<float>& buffer, int numSample
|
|||
pos = 0;
|
||||
}
|
||||
|
||||
out[i] = x * (1.0f - mix) + wet * mix;
|
||||
out[i] += wet * mix;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue