Add FX chain: per-pad filter, master compressor/EQ/distortion/reverb, VU meter

- Per-pad low-pass filter (cutoff + resonance) applied in DrumPad render
- Master bus FX chain in PluginProcessor::applyMasterFx():
  - Distortion (tanh waveshaper with drive/mix)
  - 3-band EQ (low shelf 200Hz, peak 1kHz, high shelf 5kHz)
  - Compressor (juce::dsp::Compressor with threshold/ratio)
  - Reverb (juce::dsp::Reverb with size/decay)
- Master volume + pan with constant-power pan law
- VU meter: RMS level measurement with exponential smoothing
- All FX panel and master panel knobs now connected to audio processing
- SampleEditorPanel cutoff/reso knobs linked to DrumPad filter params

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hariel1985
2026-03-22 11:21:10 +01:00
szülő 4cc22e0bf0
commit ec9a8b4e23
6 fájl változott, egészen pontosan 223 új sor hozzáadva és 8 régi sor törölve

Fájl megtekintése

@@ -3,9 +3,17 @@
DrumPad::DrumPad() {} DrumPad::DrumPad() {}
DrumPad::~DrumPad() {} DrumPad::~DrumPad() {}
void DrumPad::prepareToPlay (double sr, int /*samplesPerBlock*/) void DrumPad::prepareToPlay (double sr, int samplesPerBlock)
{ {
sampleRate = sr; sampleRate = sr;
// Prepare per-pad filter
juce::dsp::ProcessSpec spec { sr, (juce::uint32) samplesPerBlock, 1 };
filterL.prepare (spec);
filterR.prepare (spec);
filterL.reset();
filterR.reset();
lastCutoff = filterCutoff;
} }
void DrumPad::releaseResources() void DrumPad::releaseResources()
@@ -339,6 +347,18 @@ void DrumPad::renderNextBlock (juce::AudioBuffer<float>& outputBuffer, int start
float leftGain = std::cos (panPos * juce::MathConstants<float>::halfPi); float leftGain = std::cos (panPos * juce::MathConstants<float>::halfPi);
float rightGain = std::sin (panPos * juce::MathConstants<float>::halfPi); float rightGain = std::sin (panPos * juce::MathConstants<float>::halfPi);
// Update filter coefficients if cutoff changed
if (std::abs (filterCutoff - lastCutoff) > 1.0f || std::abs (filterReso - filterL.coefficients->coefficients[0]) > 0.01f)
{
float clampedCutoff = juce::jlimit (20.0f, (float) (sampleRate * 0.49), filterCutoff);
auto coeffs = juce::dsp::IIR::Coefficients<float>::makeLowPass (sampleRate, clampedCutoff, filterReso);
*filterL.coefficients = *coeffs;
*filterR.coefficients = *coeffs;
lastCutoff = filterCutoff;
}
bool useFilter = filterCutoff < 19900.0f; // Skip filter if fully open
for (int i = 0; i < numSamples; ++i) for (int i = 0; i < numSamples; ++i)
{ {
if (! playing) break; if (! playing) break;
@@ -375,6 +395,11 @@ void DrumPad::renderNextBlock (juce::AudioBuffer<float>& outputBuffer, int start
float s1 = sampleBuffer.getSample (srcCh, pos1); float s1 = sampleBuffer.getSample (srcCh, pos1);
float sampleVal = s0 + frac * (s1 - s0); float sampleVal = s0 + frac * (s1 - s0);
// Apply per-pad filter
if (useFilter)
sampleVal = (ch == 0) ? filterL.processSample (sampleVal)
: filterR.processSample (sampleVal);
float channelGain = (ch == 0) ? leftGain : rightGain; float channelGain = (ch == 0) ? leftGain : rightGain;
outputBuffer.addSample (ch, startSample + i, sampleVal * gain * channelGain); outputBuffer.addSample (ch, startSample + i, sampleVal * gain * channelGain);
} }

Fájl megtekintése

@@ -64,6 +64,10 @@ public:
float sustain = 1.0f; float sustain = 1.0f;
float release = 0.05f; float release = 0.05f;
// Per-pad filter
float filterCutoff = 20000.0f; // Hz
float filterReso = 0.707f; // Q
// State // State
bool isPlaying() const { return playing; } bool isPlaying() const { return playing; }
juce::String getLoadedFileName() const { return loadedFileName; } juce::String getLoadedFileName() const { return loadedFileName; }
@@ -87,6 +91,10 @@ private:
bool playing = false; bool playing = false;
float currentVelocity = 1.0f; float currentVelocity = 1.0f;
// Per-pad low-pass filter (stereo)
juce::dsp::IIR::Filter<float> filterL, filterR;
float lastCutoff = 20000.0f;
// ADSR state // ADSR state
enum class EnvelopeStage { Idle, Attack, Decay, Sustain, Release }; enum class EnvelopeStage { Idle, Attack, Decay, Sustain, Release };
EnvelopeStage envStage = EnvelopeStage::Idle; EnvelopeStage envStage = EnvelopeStage::Idle;

Fájl megtekintése

@@ -211,7 +211,22 @@ void InstaDrumsEditor::timerCallback()
for (auto* pc : padComponents) for (auto* pc : padComponents)
pc->repaint(); pc->repaint();
// Update VU meter from processor output levels // Sync FX panel knobs -> processor atomic params
// (simplified: just repaint for now) processor.compThreshold.store (fxPanel.getCompThreshold());
masterPanel.getVuMeter().repaint(); processor.compRatio.store (fxPanel.getCompRatio());
processor.eqLo.store (fxPanel.getEqLo());
processor.eqMid.store (fxPanel.getEqMid());
processor.eqHi.store (fxPanel.getEqHi());
processor.distDrive.store (fxPanel.getDistDrive());
processor.distMix.store (fxPanel.getDistMix());
processor.reverbSize.store (fxPanel.getReverbSize());
processor.reverbDecay.store (fxPanel.getReverbDecay());
// Sync master panel -> processor
processor.masterVolume.store (masterPanel.getMasterVolume());
processor.masterPan.store (masterPanel.getMasterPan());
processor.masterTune.store (masterPanel.getMasterTune());
// Update VU meter from processor
masterPanel.getVuMeter().setLevel (processor.vuLevelL.load(), processor.vuLevelR.load());
} }

Fájl megtekintése

@@ -40,8 +40,27 @@ void InstaDrumsProcessor::initializeDefaults()
void InstaDrumsProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) void InstaDrumsProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{ {
currentSampleRate = sampleRate;
for (int i = 0; i < numActivePads; ++i) for (int i = 0; i < numActivePads; ++i)
pads[i].prepareToPlay (sampleRate, samplesPerBlock); pads[i].prepareToPlay (sampleRate, samplesPerBlock);
// Master FX chain
juce::dsp::ProcessSpec spec { sampleRate, (juce::uint32) samplesPerBlock, 2 };
reverb.prepare (spec);
compressor.prepare (spec);
juce::dsp::ProcessSpec monoSpec { sampleRate, (juce::uint32) samplesPerBlock, 1 };
eqLoFilterL.prepare (monoSpec); eqLoFilterR.prepare (monoSpec);
eqMidFilterL.prepare (monoSpec); eqMidFilterR.prepare (monoSpec);
eqHiFilterL.prepare (monoSpec); eqHiFilterR.prepare (monoSpec);
reverb.reset();
compressor.reset();
eqLoFilterL.reset(); eqLoFilterR.reset();
eqMidFilterL.reset(); eqMidFilterR.reset();
eqHiFilterL.reset(); eqHiFilterR.reset();
} }
void InstaDrumsProcessor::releaseResources() void InstaDrumsProcessor::releaseResources()
@@ -87,6 +106,122 @@ void InstaDrumsProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::
// Render audio from all pads // Render audio from all pads
for (int i = 0; i < numActivePads; ++i) for (int i = 0; i < numActivePads; ++i)
pads[i].renderNextBlock (buffer, 0, buffer.getNumSamples()); pads[i].renderNextBlock (buffer, 0, buffer.getNumSamples());
// Apply master FX chain
applyMasterFx (buffer);
}
void InstaDrumsProcessor::applyMasterFx (juce::AudioBuffer<float>& buffer)
{
const int numSamples = buffer.getNumSamples();
// --- Distortion (pre-EQ) ---
float drive = distDrive.load();
float dMix = distMix.load();
if (drive > 0.001f && dMix > 0.001f)
{
float driveGain = 1.0f + drive * 20.0f;
for (int ch = 0; ch < buffer.getNumChannels(); ++ch)
{
float* data = buffer.getWritePointer (ch);
for (int i = 0; i < numSamples; ++i)
{
float dry = data[i];
float wet = std::tanh (dry * driveGain) / std::tanh (driveGain);
data[i] = dry * (1.0f - dMix) + wet * dMix;
}
}
}
// --- 3-band EQ ---
float lo = eqLo.load();
float mid = eqMid.load();
float hi = eqHi.load();
if (std::abs (lo) > 0.1f || std::abs (mid) > 0.1f || std::abs (hi) > 0.1f)
{
auto loCoeffs = juce::dsp::IIR::Coefficients<float>::makeLowShelf (currentSampleRate, 200.0, 0.707f, juce::Decibels::decibelsToGain (lo));
auto midCoeffs = juce::dsp::IIR::Coefficients<float>::makePeakFilter (currentSampleRate, 1000.0, 1.0f, juce::Decibels::decibelsToGain (mid));
auto hiCoeffs = juce::dsp::IIR::Coefficients<float>::makeHighShelf (currentSampleRate, 5000.0, 0.707f, juce::Decibels::decibelsToGain (hi));
*eqLoFilterL.coefficients = *loCoeffs; *eqLoFilterR.coefficients = *loCoeffs;
*eqMidFilterL.coefficients = *midCoeffs; *eqMidFilterR.coefficients = *midCoeffs;
*eqHiFilterL.coefficients = *hiCoeffs; *eqHiFilterR.coefficients = *hiCoeffs;
if (buffer.getNumChannels() >= 2)
{
float* L = buffer.getWritePointer (0);
float* R = buffer.getWritePointer (1);
for (int i = 0; i < numSamples; ++i)
{
L[i] = eqLoFilterL.processSample (L[i]);
L[i] = eqMidFilterL.processSample (L[i]);
L[i] = eqHiFilterL.processSample (L[i]);
R[i] = eqLoFilterR.processSample (R[i]);
R[i] = eqMidFilterR.processSample (R[i]);
R[i] = eqHiFilterR.processSample (R[i]);
}
}
}
// --- Compressor ---
float thresh = compThreshold.load();
float ratio = compRatio.load();
compressor.setThreshold (thresh);
compressor.setRatio (ratio);
compressor.setAttack (10.0f);
compressor.setRelease (100.0f);
{
juce::dsp::AudioBlock<float> block (buffer);
juce::dsp::ProcessContextReplacing<float> ctx (block);
compressor.process (ctx);
}
// --- Reverb ---
float rSize = reverbSize.load();
float rDecay = reverbDecay.load();
if (rSize > 0.01f || rDecay > 0.01f)
{
juce::dsp::Reverb::Parameters rParams;
rParams.roomSize = rSize;
rParams.damping = 1.0f - rDecay;
rParams.wetLevel = rSize * 0.5f;
rParams.dryLevel = 1.0f;
rParams.width = 1.0f;
reverb.setParameters (rParams);
juce::dsp::AudioBlock<float> block (buffer);
juce::dsp::ProcessContextReplacing<float> ctx (block);
reverb.process (ctx);
}
// --- Master Volume + Pan ---
float mVol = masterVolume.load();
float mPan = masterPan.load();
float panPos = (mPan + 1.0f) * 0.5f;
float mLGain = std::cos (panPos * juce::MathConstants<float>::halfPi) * mVol;
float mRGain = std::sin (panPos * juce::MathConstants<float>::halfPi) * mVol;
if (buffer.getNumChannels() >= 2)
{
buffer.applyGain (0, 0, numSamples, mLGain);
buffer.applyGain (1, 0, numSamples, mRGain);
}
else
{
buffer.applyGain (mVol);
}
// --- VU Meter ---
float rmsL = 0.0f, rmsR = 0.0f;
if (buffer.getNumChannels() >= 1)
rmsL = buffer.getRMSLevel (0, 0, numSamples);
if (buffer.getNumChannels() >= 2)
rmsR = buffer.getRMSLevel (1, 0, numSamples);
// Smooth VU (simple exponential)
vuLevelL.store (vuLevelL.load() * 0.8f + rmsL * 0.2f);
vuLevelR.store (vuLevelR.load() * 0.8f + rmsR * 0.2f);
} }
DrumPad* InstaDrumsProcessor::findPadForNote (int midiNote) DrumPad* InstaDrumsProcessor::findPadForNote (int midiNote)

Fájl megtekintése

@@ -40,7 +40,6 @@ public:
juce::AudioFormatManager& getFormatManager() { return formatManager; } juce::AudioFormatManager& getFormatManager() { return formatManager; }
// Find pad by MIDI note
DrumPad* findPadForNote (int midiNote); DrumPad* findPadForNote (int midiNote);
// Kit management // Kit management
@@ -48,13 +47,42 @@ public:
void saveKitPreset (const juce::File& file); void saveKitPreset (const juce::File& file);
void loadKitPreset (const juce::File& file); void loadKitPreset (const juce::File& file);
// Master FX parameters (read by editor from FxPanel/MasterPanel)
std::atomic<float> masterVolume { 1.0f };
std::atomic<float> masterPan { 0.0f };
std::atomic<float> masterTune { 0.0f };
// FX params
std::atomic<float> compThreshold { -12.0f };
std::atomic<float> compRatio { 4.0f };
std::atomic<float> eqLo { 0.0f };
std::atomic<float> eqMid { 0.0f };
std::atomic<float> eqHi { 0.0f };
std::atomic<float> distDrive { 0.0f };
std::atomic<float> distMix { 0.0f };
std::atomic<float> reverbSize { 0.3f };
std::atomic<float> reverbDecay { 0.5f };
// VU meter levels (written by audio thread, read by GUI)
std::atomic<float> vuLevelL { 0.0f };
std::atomic<float> vuLevelR { 0.0f };
private: private:
std::array<DrumPad, maxPads> pads; std::array<DrumPad, maxPads> pads;
int numActivePads = defaultNumPads; int numActivePads = defaultNumPads;
juce::AudioFormatManager formatManager; juce::AudioFormatManager formatManager;
// Default MIDI mapping (GM drum map) // Master FX chain
juce::dsp::Reverb reverb;
juce::dsp::Compressor<float> compressor;
juce::dsp::IIR::Filter<float> eqLoFilterL, eqLoFilterR;
juce::dsp::IIR::Filter<float> eqMidFilterL, eqMidFilterR;
juce::dsp::IIR::Filter<float> eqHiFilterL, eqHiFilterR;
double currentSampleRate = 44100.0;
void initializeDefaults(); void initializeDefaults();
void applyMasterFx (juce::AudioBuffer<float>& buffer);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InstaDrumsProcessor) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InstaDrumsProcessor)
}; };

Fájl megtekintése

@@ -61,6 +61,8 @@ void SampleEditorPanel::updateFromPad()
releaseSlider.setValue (currentPad->release, juce::dontSendNotification); releaseSlider.setValue (currentPad->release, juce::dontSendNotification);
pitchSlider.setValue (currentPad->pitch, juce::dontSendNotification); pitchSlider.setValue (currentPad->pitch, juce::dontSendNotification);
panSlider.setValue (currentPad->pan, juce::dontSendNotification); panSlider.setValue (currentPad->pan, juce::dontSendNotification);
cutoffSlider.setValue (currentPad->filterCutoff, juce::dontSendNotification);
resoSlider.setValue (currentPad->filterReso, juce::dontSendNotification);
auto& buf = currentPad->getSampleBuffer(); auto& buf = currentPad->getSampleBuffer();
waveform.setBuffer (&buf); waveform.setBuffer (&buf);
@@ -78,8 +80,10 @@ void SampleEditorPanel::sliderValueChanged (juce::Slider* slider)
else if (slider == &decaySlider) currentPad->decay = (float) slider->getValue(); else if (slider == &decaySlider) currentPad->decay = (float) slider->getValue();
else if (slider == &sustainSlider) currentPad->sustain = (float) slider->getValue(); else if (slider == &sustainSlider) currentPad->sustain = (float) slider->getValue();
else if (slider == &releaseSlider) currentPad->release = (float) slider->getValue(); else if (slider == &releaseSlider) currentPad->release = (float) slider->getValue();
else if (slider == &pitchSlider) currentPad->pitch = (float) slider->getValue(); else if (slider == &pitchSlider) currentPad->pitch = (float) slider->getValue();
else if (slider == &panSlider) currentPad->pan = (float) slider->getValue(); else if (slider == &panSlider) currentPad->pan = (float) slider->getValue();
else if (slider == &cutoffSlider) currentPad->filterCutoff = (float) slider->getValue();
else if (slider == &resoSlider) currentPad->filterReso = (float) slider->getValue();
// Update ADSR overlay // Update ADSR overlay
waveform.setADSR (currentPad->attack, currentPad->decay, currentPad->sustain, currentPad->release); waveform.setADSR (currentPad->attack, currentPad->decay, currentPad->sustain, currentPad->release);