mirror of
https://codeberg.org/armin/chromaflock.git
synced 2026-09-01 12:20:47 +02:00
Fix filter env: restore unipolar modulation so all env knobs are responsive
Reverts the sustain-relative modulation that made F.ENV SUS and the cutoff knob feel dead while a note was held. The envelope now modulates up from the base cutoff; at env amount 0 the cutoff equals the knob, and lowering env amount darkens the held tone as expected. Also extends the filter to 24/48 dB modes (extra TPT stages), adds the matching combobox options, gives knobs a real blurred drop shadow, and updates the AGENTS.md filterType choice count.
This commit is contained in:
parent
d4e738c9f1
commit
a1cd68b6b3
5 changed files with 38 additions and 18 deletions
|
|
@ -101,7 +101,7 @@ Osc1 + Osc2 → Amplitude Envelope × Velocity → Filter (cutoff modulated by F
|
||||||
| osc2Fine | OSC2 Fine | -100..100 | 7 |
|
| osc2Fine | OSC2 Fine | -100..100 | 7 |
|
||||||
| osc2Level | OSC2 Level | 0..1 | 0.7 |
|
| osc2Level | OSC2 Level | 0..1 | 0.7 |
|
||||||
| phaseOffset | Phase Offset | 0..1 | 0.5 |
|
| phaseOffset | Phase Offset | 0..1 | 0.5 |
|
||||||
| filterType | Filter Type | Choice(5) | 0 (LP12)|
|
| filterType | Filter Type | Choice(9) | 0 (LP12)|
|
||||||
| filterCutoff | Filter Cutoff | 20..20000 (log) | 8000 |
|
| filterCutoff | Filter Cutoff | 20..20000 (log) | 8000 |
|
||||||
| filterRes | Filter Res | 0..1 | 0.3 |
|
| filterRes | Filter Res | 0..1 | 0.3 |
|
||||||
| filterEnvAmt | Filter Env Amt | 0..1 | 0 |
|
| filterEnvAmt | Filter Env Amt | 0..1 | 0 |
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
enum class FilterType { LowPass12 = 0, LowPass24, BandPass, HighPass, Notch, NumTypes };
|
enum class FilterType { LowPass12 = 0, LowPass24, BandPass, HighPass, Notch,
|
||||||
|
BandPass24, HighPass24, Notch24, LowPass48, NumTypes };
|
||||||
|
|
||||||
class Filter {
|
class Filter {
|
||||||
public:
|
public:
|
||||||
|
|
@ -12,7 +13,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
for (int i = 0; i < 4; ++i)
|
for (int i = 0; i < 8; ++i)
|
||||||
stage[i] = 0.0f;
|
stage[i] = 0.0f;
|
||||||
gCoeff = 0.0f;
|
gCoeff = 0.0f;
|
||||||
gTarget = 0.0f;
|
gTarget = 0.0f;
|
||||||
|
|
@ -52,12 +53,30 @@ public:
|
||||||
stage[2] = g * hp2 + bp2;
|
stage[2] = g * hp2 + bp2;
|
||||||
stage[3] = g * bp2 + lp2;
|
stage[3] = g * bp2 + lp2;
|
||||||
|
|
||||||
|
// Stage 3 (fed by the stage-2 lowpass) — 36 dB
|
||||||
|
float hp3 = (lp2 - (k + g) * stage[4] - stage[5]) / denom;
|
||||||
|
float bp3 = g * hp3 + stage[4];
|
||||||
|
float lp3 = g * bp3 + stage[5];
|
||||||
|
stage[4] = g * hp3 + bp3;
|
||||||
|
stage[5] = g * bp3 + lp3;
|
||||||
|
|
||||||
|
// Stage 4 (fed by the stage-3 lowpass) — 48 dB
|
||||||
|
float hp4 = (lp3 - (k + g) * stage[6] - stage[7]) / denom;
|
||||||
|
float bp4 = g * hp4 + stage[6];
|
||||||
|
float lp4 = g * bp4 + stage[7];
|
||||||
|
stage[6] = g * hp4 + bp4;
|
||||||
|
stage[7] = g * bp4 + lp4;
|
||||||
|
|
||||||
switch (filterType) {
|
switch (filterType) {
|
||||||
case FilterType::LowPass12: return lp;
|
case FilterType::LowPass12: return lp;
|
||||||
case FilterType::LowPass24: return lp2;
|
case FilterType::LowPass24: return lp2;
|
||||||
case FilterType::BandPass: return bp;
|
case FilterType::BandPass: return bp;
|
||||||
case FilterType::HighPass: return hp;
|
case FilterType::HighPass: return hp;
|
||||||
case FilterType::Notch: return input - bp;
|
case FilterType::Notch: return input - bp;
|
||||||
|
case FilterType::BandPass24: return bp2;
|
||||||
|
case FilterType::HighPass24: return hp2;
|
||||||
|
case FilterType::Notch24: return input - bp2;
|
||||||
|
case FilterType::LowPass48: return lp4;
|
||||||
case FilterType::NumTypes: return input;
|
case FilterType::NumTypes: return input;
|
||||||
}
|
}
|
||||||
return lp;
|
return lp;
|
||||||
|
|
@ -66,7 +85,7 @@ public:
|
||||||
private:
|
private:
|
||||||
static constexpr float pi = 3.14159265358979323846f;
|
static constexpr float pi = 3.14159265358979323846f;
|
||||||
double sampleRate = 44100.0;
|
double sampleRate = 44100.0;
|
||||||
float stage[4] = {0.0f, 0.0f, 0.0f, 0.0f};
|
float stage[8] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
|
||||||
|
|
||||||
float gCoeff = 0.0f, gTarget = 0.0f;
|
float gCoeff = 0.0f, gTarget = 0.0f;
|
||||||
float kCoeff = 2.0f, kTarget = 2.0f;
|
float kCoeff = 2.0f, kTarget = 2.0f;
|
||||||
|
|
|
||||||
|
|
@ -129,13 +129,12 @@ public:
|
||||||
right *= noteVelocity * envSample;
|
right *= noteVelocity * envSample;
|
||||||
|
|
||||||
// Filter cutoff with env + keytrack.
|
// Filter cutoff with env + keytrack.
|
||||||
// Modulate RELATIVE to the envelope's sustain level so the held
|
// Unipolar modulation UP from the base cutoff: the envelope (0..1)
|
||||||
// (sustain) cutoff always equals the base cutoff knob regardless of
|
// multiplied by env amount adds brightness on top of the cutoff
|
||||||
// env amount — the envelope only opens above (attack) and closes
|
// knob, never going below it. Every env knob (attack/decay/
|
||||||
// below (release) that centre. This keeps the cutoff knob audible
|
// sustain/release) shapes the response, and at env amount 0 the
|
||||||
// at any env amount instead of collapsing when env amt is lowered.
|
// cutoff equals the knob.
|
||||||
float modCutoff = filterCutoff * std::pow(2.0f,
|
float modCutoff = filterCutoff * std::pow(2.0f, fEnvSample * filterEnvAmount * 4.0f);
|
||||||
(fEnvSample - filterEnvSustain) * filterEnvAmount * 4.0f);
|
|
||||||
float keyTrackFactor = std::pow(2.0f, (static_cast<float>(currentMidiNote) - 60.0f) / 12.0f * keyTrack);
|
float keyTrackFactor = std::pow(2.0f, (static_cast<float>(currentMidiNote) - 60.0f) / 12.0f * keyTrack);
|
||||||
modCutoff *= keyTrackFactor;
|
modCutoff *= keyTrackFactor;
|
||||||
|
|
||||||
|
|
@ -197,7 +196,6 @@ public:
|
||||||
filterResonance = res;
|
filterResonance = res;
|
||||||
filterType = ft;
|
filterType = ft;
|
||||||
filterEnvAmount = fEnvAmt;
|
filterEnvAmount = fEnvAmt;
|
||||||
filterEnvSustain = fSus;
|
|
||||||
keyTrack = kTrack;
|
keyTrack = kTrack;
|
||||||
pan = p;
|
pan = p;
|
||||||
drive = std::max(drv, 1.001f);
|
drive = std::max(drv, 1.001f);
|
||||||
|
|
@ -252,7 +250,6 @@ private:
|
||||||
float filterResonance = 0.7f;
|
float filterResonance = 0.7f;
|
||||||
FilterType filterType = FilterType::LowPass12;
|
FilterType filterType = FilterType::LowPass12;
|
||||||
float filterEnvAmount = 0.0f;
|
float filterEnvAmount = 0.0f;
|
||||||
float filterEnvSustain = 0.5f;
|
|
||||||
float keyTrack = 0.5f;
|
float keyTrack = 0.5f;
|
||||||
float pan = 0.0f;
|
float pan = 0.0f;
|
||||||
float drive = 1.5f;
|
float drive = 1.5f;
|
||||||
|
|
|
||||||
|
|
@ -47,9 +47,11 @@ void KnobLookAndFeel::drawRotarySlider(juce::Graphics& g, int x, int y, int widt
|
||||||
auto rw = radius * 2.0f;
|
auto rw = radius * 2.0f;
|
||||||
auto angle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle);
|
auto angle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle);
|
||||||
|
|
||||||
// Drop shadow
|
// Blurred drop shadow
|
||||||
g.setColour(juce::Colour(0x40000000));
|
juce::DropShadow knobShadow(juce::Colour(0x55000000), 10, juce::Point<int>(0, 5));
|
||||||
g.fillEllipse(rx + 2.0f, ry + 3.0f, rw, rw);
|
juce::Path shadowPath;
|
||||||
|
shadowPath.addEllipse(juce::Rectangle<float>(rx, ry, rw, rw));
|
||||||
|
knobShadow.drawForPath(g, shadowPath);
|
||||||
|
|
||||||
// Outer bevel (dark bottom-right, light top-left)
|
// Outer bevel (dark bottom-right, light top-left)
|
||||||
auto bevelPath = juce::Path();
|
auto bevelPath = juce::Path();
|
||||||
|
|
@ -620,7 +622,8 @@ MainContentComponent::MainContentComponent(ChromaFlockProcessor& p)
|
||||||
setupParam(phaseOffsetKnob, phaseOffsetAttach, "phaseOffset", "PHASE");
|
setupParam(phaseOffsetKnob, phaseOffsetAttach, "phaseOffset", "PHASE");
|
||||||
|
|
||||||
setupLabel(filterLabel, "FILTER");
|
setupLabel(filterLabel, "FILTER");
|
||||||
setupCB(filterTypeBox, filterTypeAttach, "filterType", {"LP 12dB", "LP 24dB", "Band Pass", "High Pass", "Notch"});
|
setupCB(filterTypeBox, filterTypeAttach, "filterType", {"LP 12dB", "LP 24dB", "Band Pass", "High Pass", "Notch",
|
||||||
|
"Band Pass 24", "High Pass 24", "Notch 24", "LP 48dB"});
|
||||||
setupParam(filterCutoffKnob, filterCutoffAttach, "filterCutoff", "CUTOFF");
|
setupParam(filterCutoffKnob, filterCutoffAttach, "filterCutoff", "CUTOFF");
|
||||||
setupParam(filterResKnob, filterResAttach, "filterRes", "RES");
|
setupParam(filterResKnob, filterResAttach, "filterRes", "RES");
|
||||||
setupParam(filterEnvAmtKnob, filterEnvAmtAttach, "filterEnvAmt", "ENV AMT");
|
setupParam(filterEnvAmtKnob, filterEnvAmtAttach, "filterEnvAmt", "ENV AMT");
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,8 @@ juce::AudioProcessorValueTreeState::ParameterLayout ChromaFlockProcessor::create
|
||||||
// FILTER
|
// FILTER
|
||||||
layout.add(std::make_unique<juce::AudioParameterChoice>(
|
layout.add(std::make_unique<juce::AudioParameterChoice>(
|
||||||
juce::ParameterID{"filterType", 1}, "Filter Type",
|
juce::ParameterID{"filterType", 1}, "Filter Type",
|
||||||
juce::StringArray{"LP 12dB", "LP 24dB", "Band Pass", "High Pass", "Notch"}, 0));
|
juce::StringArray{"LP 12dB", "LP 24dB", "Band Pass", "High Pass", "Notch",
|
||||||
|
"Band Pass 24", "High Pass 24", "Notch 24", "LP 48dB"}, 0));
|
||||||
layout.add(std::make_unique<juce::AudioParameterFloat>(
|
layout.add(std::make_unique<juce::AudioParameterFloat>(
|
||||||
juce::ParameterID{"filterCutoff", 1}, "Filter Cutoff",
|
juce::ParameterID{"filterCutoff", 1}, "Filter Cutoff",
|
||||||
juce::NormalisableRange<float>(
|
juce::NormalisableRange<float>(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue