Per-pad FX chain, animated toggles, GR meter, simplified master panel

- FX moved from master bus to per-pad processing:
  each pad has its own Filter, Distortion, EQ, Compressor, Reverb
  via DrumPad::applyPadFx() with temp buffer rendering
- FxPanel now edits the selected pad's FX parameters
- Animated toggle switches with smooth lerp transition and glow
- Per-pad compressor GR meter connected to FxPanel display
- Master panel simplified: Volume/Tune/Pan + Limiter toggle + VU meter
- Master bus chain: Vol/Pan → Output Limiter (0dB brickwall) → VU
- Pointer glow reduced to half intensity (4 layers, narrower spread)
- Smooth 8-layer arc glow with exponential opacity falloff

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hariel1985
2026-03-23 06:42:47 +01:00
szülő 20b9fe2674
commit a0e83fa0a4
11 fájl változott, egészen pontosan 500 új sor hozzáadva és 210 régi sor törölve

Fájl megtekintése

@@ -6,14 +6,29 @@ DrumPad::~DrumPad() {}
void DrumPad::prepareToPlay (double sr, int samplesPerBlock) void DrumPad::prepareToPlay (double sr, int samplesPerBlock)
{ {
sampleRate = sr; sampleRate = sr;
blockSize = samplesPerBlock;
// Prepare per-pad filter tempBuffer.setSize (2, samplesPerBlock);
juce::dsp::ProcessSpec spec { sr, (juce::uint32) samplesPerBlock, 1 };
filterL.prepare (spec); // Per-pad filter
filterR.prepare (spec); juce::dsp::ProcessSpec monoSpec { sr, (juce::uint32) samplesPerBlock, 1 };
filterL.reset(); filterL.prepare (monoSpec); filterR.prepare (monoSpec);
filterR.reset(); filterL.reset(); filterR.reset();
lastCutoff = filterCutoff; lastCutoff = filterCutoff;
// Per-pad FX
juce::dsp::ProcessSpec stereoSpec { sr, (juce::uint32) samplesPerBlock, 2 };
padCompressor.prepare (stereoSpec);
padCompressor.reset();
padReverb.prepare (stereoSpec);
padReverb.reset();
padEqLoL.prepare (monoSpec); padEqLoR.prepare (monoSpec);
padEqMidL.prepare (monoSpec); padEqMidR.prepare (monoSpec);
padEqHiL.prepare (monoSpec); padEqHiR.prepare (monoSpec);
padEqLoL.reset(); padEqLoR.reset();
padEqMidL.reset(); padEqMidR.reset();
padEqHiL.reset(); padEqHiR.reset();
} }
void DrumPad::releaseResources() void DrumPad::releaseResources()
@@ -335,6 +350,12 @@ void DrumPad::renderNextBlock (juce::AudioBuffer<float>& outputBuffer, int start
if (! playing || activeSample == nullptr) if (! playing || activeSample == nullptr)
return; return;
// Ensure temp buffer is large enough
if (tempBuffer.getNumSamples() < numSamples)
tempBuffer.setSize (2, numSamples, false, false, true);
tempBuffer.clear (0, numSamples);
const auto& sampleBuffer = activeSample->buffer; const auto& sampleBuffer = activeSample->buffer;
const int sampleLength = sampleBuffer.getNumSamples(); const int sampleLength = sampleBuffer.getNumSamples();
const int srcChannels = sampleBuffer.getNumChannels(); const int srcChannels = sampleBuffer.getNumChannels();
@@ -348,7 +369,7 @@ void DrumPad::renderNextBlock (juce::AudioBuffer<float>& outputBuffer, int start
float rightGain = std::sin (panPos * juce::MathConstants<float>::halfPi); float rightGain = std::sin (panPos * juce::MathConstants<float>::halfPi);
// Update filter coefficients if cutoff changed // Update filter coefficients if cutoff changed
if (std::abs (filterCutoff - lastCutoff) > 1.0f || std::abs (filterReso - filterL.coefficients->coefficients[0]) > 0.01f) if (std::abs (filterCutoff - lastCutoff) > 1.0f)
{ {
float clampedCutoff = juce::jlimit (20.0f, (float) (sampleRate * 0.49), filterCutoff); float clampedCutoff = juce::jlimit (20.0f, (float) (sampleRate * 0.49), filterCutoff);
auto coeffs = juce::dsp::IIR::Coefficients<float>::makeLowPass (sampleRate, clampedCutoff, filterReso); auto coeffs = juce::dsp::IIR::Coefficients<float>::makeLowPass (sampleRate, clampedCutoff, filterReso);
@@ -357,8 +378,9 @@ void DrumPad::renderNextBlock (juce::AudioBuffer<float>& outputBuffer, int start
lastCutoff = filterCutoff; lastCutoff = filterCutoff;
} }
bool useFilter = filterCutoff < 19900.0f; // Skip filter if fully open bool useFilter = filterCutoff < 19900.0f;
// Render into temp buffer
for (int i = 0; i < numSamples; ++i) for (int i = 0; i < numSamples; ++i)
{ {
if (! playing) break; if (! playing) break;
@@ -388,23 +410,119 @@ void DrumPad::renderNextBlock (juce::AudioBuffer<float>& outputBuffer, int start
int pos1 = std::min (pos0 + 1, sampleLength - 1); int pos1 = std::min (pos0 + 1, sampleLength - 1);
float frac = (float) (readPosition - (double) pos0); float frac = (float) (readPosition - (double) pos0);
for (int ch = 0; ch < outputBuffer.getNumChannels(); ++ch) for (int ch = 0; ch < 2; ++ch)
{ {
int srcCh = std::min (ch, srcChannels - 1); int srcCh = std::min (ch, srcChannels - 1);
float s0 = sampleBuffer.getSample (srcCh, pos0); float s0 = sampleBuffer.getSample (srcCh, pos0);
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) if (useFilter)
sampleVal = (ch == 0) ? filterL.processSample (sampleVal) sampleVal = (ch == 0) ? filterL.processSample (sampleVal)
: filterR.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); tempBuffer.setSample (ch, i, sampleVal * gain * channelGain);
} }
} }
readPosition += pitchRatio; readPosition += pitchRatio;
} }
// Apply per-pad FX chain to temp buffer
applyPadFx (tempBuffer, numSamples);
// Mix temp buffer into output
for (int ch = 0; ch < outputBuffer.getNumChannels(); ++ch)
outputBuffer.addFrom (ch, startSample, tempBuffer, std::min (ch, 1), 0, numSamples);
}
// ============================================================
// Per-pad FX chain
// ============================================================
void DrumPad::applyPadFx (juce::AudioBuffer<float>& buf, int numSamples)
{
// --- Distortion ---
if (fxDistEnabled && fxDistDrive > 0.001f && fxDistMix > 0.001f)
{
float driveGain = 1.0f + fxDistDrive * 20.0f;
for (int ch = 0; ch < buf.getNumChannels(); ++ch)
{
float* data = buf.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 - fxDistMix) + wet * fxDistMix;
}
}
}
// --- EQ ---
if (fxEqEnabled && (std::abs (fxEqLo) > 0.1f || std::abs (fxEqMid) > 0.1f || std::abs (fxEqHi) > 0.1f))
{
auto loC = juce::dsp::IIR::Coefficients<float>::makeLowShelf (sampleRate, 200.0, 0.707f, juce::Decibels::decibelsToGain (fxEqLo));
auto midC = juce::dsp::IIR::Coefficients<float>::makePeakFilter (sampleRate, 1000.0, 1.0f, juce::Decibels::decibelsToGain (fxEqMid));
auto hiC = juce::dsp::IIR::Coefficients<float>::makeHighShelf (sampleRate, 5000.0, 0.707f, juce::Decibels::decibelsToGain (fxEqHi));
*padEqLoL.coefficients = *loC; *padEqLoR.coefficients = *loC;
*padEqMidL.coefficients = *midC; *padEqMidR.coefficients = *midC;
*padEqHiL.coefficients = *hiC; *padEqHiR.coefficients = *hiC;
float* L = buf.getWritePointer (0);
float* R = buf.getWritePointer (1);
for (int i = 0; i < numSamples; ++i)
{
L[i] = padEqHiL.processSample (padEqMidL.processSample (padEqLoL.processSample (L[i])));
R[i] = padEqHiR.processSample (padEqMidR.processSample (padEqLoR.processSample (R[i])));
}
}
// --- Compressor ---
if (fxCompEnabled)
{
float peakLevel = 0.0f;
for (int ch = 0; ch < buf.getNumChannels(); ++ch)
peakLevel = std::max (peakLevel, buf.getMagnitude (ch, 0, numSamples));
float inputDb = juce::Decibels::gainToDecibels (peakLevel, -80.0f);
float gr = 0.0f;
if (inputDb > fxCompThreshold && fxCompRatio > 1.0f)
gr = (inputDb - fxCompThreshold) * (1.0f - 1.0f / fxCompRatio);
float prevGr = std::abs (compGainReduction.load());
if (gr > prevGr)
compGainReduction.store (-(prevGr * 0.3f + gr * 0.7f));
else
compGainReduction.store (-(prevGr * 0.92f + gr * 0.08f));
padCompressor.setThreshold (fxCompThreshold);
padCompressor.setRatio (fxCompRatio);
padCompressor.setAttack (10.0f);
padCompressor.setRelease (100.0f);
juce::dsp::AudioBlock<float> block (buf);
juce::dsp::ProcessContextReplacing<float> ctx (block);
padCompressor.process (ctx);
}
else
{
float prev = std::abs (compGainReduction.load());
compGainReduction.store (-(prev * 0.9f));
}
// --- Reverb ---
if (fxReverbEnabled && (fxReverbSize > 0.01f || fxReverbDecay > 0.01f))
{
juce::dsp::Reverb::Parameters rp;
rp.roomSize = fxReverbSize;
rp.damping = 1.0f - fxReverbDecay;
rp.wetLevel = fxReverbSize * 0.5f;
rp.dryLevel = 1.0f;
rp.width = 1.0f;
padReverb.setParameters (rp);
juce::dsp::AudioBlock<float> block (buf);
juce::dsp::ProcessContextReplacing<float> ctx (block);
padReverb.process (ctx);
}
} }

Fájl megtekintése

@@ -4,7 +4,6 @@
class DrumPad class DrumPad
{ {
public: public:
// A single sample with its audio data and source sample rate
struct Sample struct Sample
{ {
juce::AudioBuffer<float> buffer; juce::AudioBuffer<float> buffer;
@@ -12,12 +11,11 @@ public:
juce::File file; juce::File file;
}; };
// A velocity layer: velocity range + multiple round-robin samples
struct VelocityLayer struct VelocityLayer
{ {
float velocityLow = 0.0f; // 0.0 - 1.0 float velocityLow = 0.0f;
float velocityHigh = 1.0f; float velocityHigh = 1.0f;
juce::OwnedArray<Sample> samples; // round-robin variations juce::OwnedArray<Sample> samples;
int nextRoundRobin = 0; int nextRoundRobin = 0;
Sample* getNextSample() Sample* getNextSample()
@@ -35,12 +33,8 @@ public:
void prepareToPlay (double sampleRate, int samplesPerBlock); void prepareToPlay (double sampleRate, int samplesPerBlock);
void releaseResources(); void releaseResources();
// Single sample loading (backwards compatible)
void loadSample (const juce::File& file, juce::AudioFormatManager& formatManager); void loadSample (const juce::File& file, juce::AudioFormatManager& formatManager);
// Velocity layer loading from a folder
void loadLayersFromFolder (const juce::File& folder, juce::AudioFormatManager& formatManager); void loadLayersFromFolder (const juce::File& folder, juce::AudioFormatManager& formatManager);
bool hasSample() const; bool hasSample() const;
void trigger (float velocity = 1.0f); void trigger (float velocity = 1.0f);
@@ -65,8 +59,29 @@ public:
float release = 0.05f; float release = 0.05f;
// Per-pad filter // Per-pad filter
float filterCutoff = 20000.0f; // Hz float filterCutoff = 20000.0f;
float filterReso = 0.707f; // Q float filterReso = 0.707f;
// Per-pad FX parameters
bool fxCompEnabled = false;
float fxCompThreshold = -12.0f;
float fxCompRatio = 4.0f;
bool fxEqEnabled = false;
float fxEqLo = 0.0f;
float fxEqMid = 0.0f;
float fxEqHi = 0.0f;
bool fxDistEnabled = false;
float fxDistDrive = 0.0f;
float fxDistMix = 0.0f;
bool fxReverbEnabled = false;
float fxReverbSize = 0.3f;
float fxReverbDecay = 0.5f;
// Compressor GR readout (written by audio, read by GUI)
std::atomic<float> compGainReduction { 0.0f };
// State // State
bool isPlaying() const { return playing; } bool isPlaying() const { return playing; }
@@ -77,24 +92,26 @@ public:
const juce::AudioBuffer<float>& getSampleBuffer() const; const juce::AudioBuffer<float>& getSampleBuffer() const;
private: private:
// Velocity layers (sorted by velocity range)
juce::OwnedArray<VelocityLayer> layers; juce::OwnedArray<VelocityLayer> layers;
// Currently playing sample reference
Sample* activeSample = nullptr; Sample* activeSample = nullptr;
// Fallback empty buffer for getSampleBuffer when nothing loaded
juce::AudioBuffer<float> emptyBuffer; juce::AudioBuffer<float> emptyBuffer;
juce::AudioBuffer<float> tempBuffer; // for per-pad FX processing
double sampleRate = 44100.0; double sampleRate = 44100.0;
int blockSize = 512;
double readPosition = 0.0; double readPosition = 0.0;
bool playing = false; bool playing = false;
float currentVelocity = 1.0f; float currentVelocity = 1.0f;
// Per-pad low-pass filter (stereo) // Per-pad filter (stereo)
juce::dsp::IIR::Filter<float> filterL, filterR; juce::dsp::IIR::Filter<float> filterL, filterR;
float lastCutoff = 20000.0f; float lastCutoff = 20000.0f;
// Per-pad FX processors
juce::dsp::Compressor<float> padCompressor;
juce::dsp::IIR::Filter<float> padEqLoL, padEqLoR, padEqMidL, padEqMidR, padEqHiL, padEqHiR;
juce::dsp::Reverb padReverb;
// 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;
@@ -105,8 +122,8 @@ private:
void advanceEnvelope(); void advanceEnvelope();
VelocityLayer* findLayerForVelocity (float velocity); VelocityLayer* findLayerForVelocity (float velocity);
void applyPadFx (juce::AudioBuffer<float>& buf, int numSamples);
// Parse velocity tag from filename (e.g. "snare_OH_FF_1" -> FF)
static float velocityTagToLow (const juce::String& tag); static float velocityTagToLow (const juce::String& tag);
static float velocityTagToHigh (const juce::String& tag); static float velocityTagToHigh (const juce::String& tag);

Fájl megtekintése

@@ -17,6 +17,11 @@ FxPanel::FxPanel()
setupKnob (distMixSlider, distMixLabel, "Mix", 0.0, 1.0, 0.0, 0.01); setupKnob (distMixSlider, distMixLabel, "Mix", 0.0, 1.0, 0.0, 0.01);
setupKnob (reverbSizeSlider, reverbSizeLabel, "Size", 0.0, 1.0, 0.3, 0.01); setupKnob (reverbSizeSlider, reverbSizeLabel, "Size", 0.0, 1.0, 0.3, 0.01);
setupKnob (reverbDecaySlider, reverbDecayLabel, "Decay", 0.0, 1.0, 0.5, 0.01); setupKnob (reverbDecaySlider, reverbDecayLabel, "Decay", 0.0, 1.0, 0.5, 0.01);
setupToggle (compToggle);
setupToggle (eqToggle);
setupToggle (distToggle);
setupToggle (reverbToggle);
} }
void FxPanel::setupKnob (juce::Slider& s, juce::Label& l, const juce::String& name, void FxPanel::setupKnob (juce::Slider& s, juce::Label& l, const juce::String& name,
@@ -44,6 +49,63 @@ void FxPanel::setupTitle (juce::Label& l, const juce::String& text)
addAndMakeVisible (l); addAndMakeVisible (l);
} }
void FxPanel::setupToggle (juce::ToggleButton& t)
{
t.setToggleState (false, juce::dontSendNotification);
t.setButtonText ("");
addAndMakeVisible (t);
}
void FxPanel::setPad (DrumPad* pad)
{
currentPad = pad;
syncFromPad();
}
void FxPanel::syncFromPad()
{
if (currentPad == nullptr) return;
compThreshSlider.setValue (currentPad->fxCompThreshold, juce::dontSendNotification);
compRatioSlider.setValue (currentPad->fxCompRatio, juce::dontSendNotification);
compToggle.setToggleState (currentPad->fxCompEnabled, juce::dontSendNotification);
eqLoSlider.setValue (currentPad->fxEqLo, juce::dontSendNotification);
eqMidSlider.setValue (currentPad->fxEqMid, juce::dontSendNotification);
eqHiSlider.setValue (currentPad->fxEqHi, juce::dontSendNotification);
eqToggle.setToggleState (currentPad->fxEqEnabled, juce::dontSendNotification);
distDriveSlider.setValue (currentPad->fxDistDrive, juce::dontSendNotification);
distMixSlider.setValue (currentPad->fxDistMix, juce::dontSendNotification);
distToggle.setToggleState (currentPad->fxDistEnabled, juce::dontSendNotification);
reverbSizeSlider.setValue (currentPad->fxReverbSize, juce::dontSendNotification);
reverbDecaySlider.setValue (currentPad->fxReverbDecay, juce::dontSendNotification);
reverbToggle.setToggleState (currentPad->fxReverbEnabled, juce::dontSendNotification);
}
void FxPanel::syncToPad()
{
if (currentPad == nullptr) return;
currentPad->fxCompThreshold = (float) compThreshSlider.getValue();
currentPad->fxCompRatio = (float) compRatioSlider.getValue();
currentPad->fxCompEnabled = compToggle.getToggleState();
currentPad->fxEqLo = (float) eqLoSlider.getValue();
currentPad->fxEqMid = (float) eqMidSlider.getValue();
currentPad->fxEqHi = (float) eqHiSlider.getValue();
currentPad->fxEqEnabled = eqToggle.getToggleState();
currentPad->fxDistDrive = (float) distDriveSlider.getValue();
currentPad->fxDistMix = (float) distMixSlider.getValue();
currentPad->fxDistEnabled = distToggle.getToggleState();
currentPad->fxReverbSize = (float) reverbSizeSlider.getValue();
currentPad->fxReverbDecay = (float) reverbDecaySlider.getValue();
currentPad->fxReverbEnabled = reverbToggle.getToggleState();
}
void FxPanel::paint (juce::Graphics& g) void FxPanel::paint (juce::Graphics& g)
{ {
auto bounds = getLocalBounds().toFloat(); auto bounds = getLocalBounds().toFloat();
@@ -108,10 +170,18 @@ void FxPanel::resized()
distTitle.setFont (juce::FontOptions (titleSize, juce::Font::bold)); distTitle.setFont (juce::FontOptions (titleSize, juce::Font::bold));
reverbTitle.setFont (juce::FontOptions (titleSize, juce::Font::bold)); reverbTitle.setFont (juce::FontOptions (titleSize, juce::Font::bold));
int toggleW = std::max (28, (int) (36 * scale));
int toggleH = std::max (16, (int) (20 * scale));
int sectionTitleH = std::max (titleH, toggleH + 4);
auto layoutSection = [&] (juce::Rectangle<int> secArea, juce::Label& title, auto layoutSection = [&] (juce::Rectangle<int> secArea, juce::Label& title,
juce::ToggleButton& toggle,
juce::Slider* sliders[], juce::Label* labels[], int count) juce::Slider* sliders[], juce::Label* labels[], int count)
{ {
title.setBounds (secArea.removeFromTop (titleH).reduced (4, 0)); auto titleRow = secArea.removeFromTop (sectionTitleH).reduced (2, 0);
auto toggleArea = titleRow.removeFromLeft (toggleW + 4);
toggle.setBounds (toggleArea.withSizeKeepingCentre (toggleW, toggleH));
title.setBounds (titleRow);
int kw = secArea.getWidth() / count; int kw = secArea.getWidth() / count;
for (int i = 0; i < count; ++i) for (int i = 0; i < count; ++i)
{ {
@@ -122,12 +192,15 @@ void FxPanel::resized()
} }
}; };
// Top-left: Compressor // Top-left: Compressor (with GR meter area on the right)
{ {
auto sec = area.removeFromTop (rowH).removeFromLeft (halfW).reduced (4, 2); auto compFullArea = area.removeFromTop (rowH).removeFromLeft (halfW).reduced (4, 2);
int grMeterW = std::max (10, (int) (14 * scale));
compGrArea = compFullArea.removeFromRight (grMeterW);
auto sec = compFullArea;
juce::Slider* s[] = { &compThreshSlider, &compRatioSlider }; juce::Slider* s[] = { &compThreshSlider, &compRatioSlider };
juce::Label* l[] = { &compThreshLabel, &compRatioLabel }; juce::Label* l[] = { &compThreshLabel, &compRatioLabel };
layoutSection (sec, compTitle, s, l, 2); layoutSection (sec, compTitle, compToggle, s, l, 2);
} }
// Top-right: EQ (need to recalculate since we consumed area) // Top-right: EQ (need to recalculate since we consumed area)
@@ -139,7 +212,7 @@ void FxPanel::resized()
{ {
juce::Slider* s[] = { &eqLoSlider, &eqMidSlider, &eqHiSlider }; juce::Slider* s[] = { &eqLoSlider, &eqMidSlider, &eqHiSlider };
juce::Label* l[] = { &eqLoLabel, &eqMidLabel, &eqHiLabel }; juce::Label* l[] = { &eqLoLabel, &eqMidLabel, &eqHiLabel };
layoutSection (rightTop, eqTitle, s, l, 3); layoutSection (rightTop, eqTitle, eqToggle, s, l, 3);
} }
// Bottom-left: Distortion // Bottom-left: Distortion
@@ -149,7 +222,7 @@ void FxPanel::resized()
auto sec = bottomArea.removeFromLeft (halfW).reduced (4, 2); auto sec = bottomArea.removeFromLeft (halfW).reduced (4, 2);
juce::Slider* s[] = { &distDriveSlider, &distMixSlider }; juce::Slider* s[] = { &distDriveSlider, &distMixSlider };
juce::Label* l[] = { &distDriveLabel, &distMixLabel }; juce::Label* l[] = { &distDriveLabel, &distMixLabel };
layoutSection (sec, distTitle, s, l, 2); layoutSection (sec, distTitle, distToggle, s, l, 2);
} }
// Bottom-right: Reverb // Bottom-right: Reverb
@@ -157,6 +230,48 @@ void FxPanel::resized()
auto sec = bottomArea.reduced (4, 2); auto sec = bottomArea.reduced (4, 2);
juce::Slider* s[] = { &reverbSizeSlider, &reverbDecaySlider }; juce::Slider* s[] = { &reverbSizeSlider, &reverbDecaySlider };
juce::Label* l[] = { &reverbSizeLabel, &reverbDecayLabel }; juce::Label* l[] = { &reverbSizeLabel, &reverbDecayLabel };
layoutSection (sec, reverbTitle, s, l, 2); layoutSection (sec, reverbTitle, reverbToggle, s, l, 2);
} }
} }
void FxPanel::paintOverChildren (juce::Graphics& g)
{
// Draw compressor GR meter
if (compGrArea.isEmpty()) return;
auto bar = compGrArea.toFloat().reduced (2, 4);
// Background
g.setColour (juce::Colour (0xff111122));
g.fillRoundedRectangle (bar, 2.0f);
// GR bar (grows downward from top, since GR is negative)
float grNorm = juce::jlimit (0.0f, 1.0f, std::abs (compGrDb) / 30.0f); // 30dB range
float barH = bar.getHeight() * grNorm;
if (barH > 0.5f)
{
auto filled = bar.removeFromTop (barH);
// Colour: green for light GR, orange for medium, red for heavy
juce::Colour grColour;
if (grNorm < 0.3f)
grColour = juce::Colour (0xff00cc44);
else if (grNorm < 0.6f)
grColour = juce::Colour (0xffccaa00);
else
grColour = juce::Colour (0xffff4422);
g.setColour (grColour);
g.fillRoundedRectangle (filled, 2.0f);
// Glow
g.setColour (grColour.withAlpha (0.2f));
g.fillRoundedRectangle (filled.expanded (2, 0), 3.0f);
}
// "GR" label
g.setColour (InstaDrumsLookAndFeel::textSecondary.withAlpha (0.6f));
g.setFont (juce::FontOptions (8.0f));
g.drawText ("GR", compGrArea.toFloat(), juce::Justification::centredBottom);
}

Fájl megtekintése

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <JuceHeader.h> #include <JuceHeader.h>
#include "DrumPad.h"
class FxPanel : public juce::Component class FxPanel : public juce::Component
{ {
@@ -9,37 +10,47 @@ public:
void paint (juce::Graphics& g) override; void paint (juce::Graphics& g) override;
void resized() override; void resized() override;
// FX parameter getters (for processor to read) // Connect to a pad's FX params
float getCompThreshold() const { return (float) compThreshSlider.getValue(); } void setPad (DrumPad* pad);
float getCompRatio() const { return (float) compRatioSlider.getValue(); } void syncToPad(); // write GUI values to pad
float getEqLo() const { return (float) eqLoSlider.getValue(); } void syncFromPad(); // read pad values to GUI
float getEqMid() const { return (float) eqMidSlider.getValue(); }
float getEqHi() const { return (float) eqHiSlider.getValue(); } // Compressor GR meter
float getDistDrive() const { return (float) distDriveSlider.getValue(); } void setCompGainReduction (float grDb) { compGrDb = grDb; repaint (compGrArea); }
float getDistMix() const { return (float) distMixSlider.getValue(); }
float getReverbSize() const { return (float) reverbSizeSlider.getValue(); } void paintOverChildren (juce::Graphics& g) override;
float getReverbDecay() const { return (float) reverbDecaySlider.getValue(); }
private: private:
// Compressor // Compressor
juce::Slider compThreshSlider, compRatioSlider; juce::Slider compThreshSlider, compRatioSlider;
juce::Label compThreshLabel, compRatioLabel, compTitle; juce::Label compThreshLabel, compRatioLabel, compTitle;
juce::ToggleButton compToggle;
// EQ // EQ
juce::Slider eqLoSlider, eqMidSlider, eqHiSlider; juce::Slider eqLoSlider, eqMidSlider, eqHiSlider;
juce::Label eqLoLabel, eqMidLabel, eqHiLabel, eqTitle; juce::Label eqLoLabel, eqMidLabel, eqHiLabel, eqTitle;
juce::ToggleButton eqToggle;
// Distortion // Distortion
juce::Slider distDriveSlider, distMixSlider; juce::Slider distDriveSlider, distMixSlider;
juce::Label distDriveLabel, distMixLabel, distTitle; juce::Label distDriveLabel, distMixLabel, distTitle;
juce::ToggleButton distToggle;
// Reverb // Reverb
juce::Slider reverbSizeSlider, reverbDecaySlider; juce::Slider reverbSizeSlider, reverbDecaySlider;
juce::Label reverbSizeLabel, reverbDecayLabel, reverbTitle; juce::Label reverbSizeLabel, reverbDecayLabel, reverbTitle;
juce::ToggleButton reverbToggle;
DrumPad* currentPad = nullptr;
float compGrDb = 0.0f;
bool repaintCompGr = false;
juce::Rectangle<int> compGrArea; // stored from resized for paintOverChildren
void setupKnob (juce::Slider& s, juce::Label& l, const juce::String& name, void setupKnob (juce::Slider& s, juce::Label& l, const juce::String& name,
double min, double max, double val, double step = 0.01); double min, double max, double val, double step = 0.01);
void setupTitle (juce::Label& l, const juce::String& text); void setupTitle (juce::Label& l, const juce::String& text);
void setupToggle (juce::ToggleButton& t);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FxPanel) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FxPanel)
}; };

Fájl megtekintése

@@ -131,30 +131,31 @@ void InstaDrumsLookAndFeel::drawRotarySlider (juce::Graphics& g, int x, int y, i
juce::PathStrokeType::rounded)); juce::PathStrokeType::rounded));
} }
// 3. Outer arc value with scaled glow // 3. Outer arc value with smooth multi-layer glow
if (sliderPos > 0.01f) if (sliderPos > 0.01f)
{ {
juce::Path arcVal; juce::Path arcVal;
arcVal.addCentredArc (cx, cy, radius - 1, radius - 1, 0.0f, arcVal.addCentredArc (cx, cy, radius - 1, radius - 1, 0.0f,
rotaryStartAngle, angle, true); rotaryStartAngle, angle, true);
// Glow layers (scale with knob size) // Smooth glow: 8 layers from wide/faint to narrow/bright
g.setColour (arcColour.withAlpha (0.08f)); const int numGlowLayers = 8;
g.strokePath (arcVal, juce::PathStrokeType (glowW1, juce::PathStrokeType::curved, for (int i = 0; i < numGlowLayers; ++i)
juce::PathStrokeType::rounded)); {
g.setColour (arcColour.withAlpha (0.15f)); float t = (float) i / (float) (numGlowLayers - 1); // 0.0 (outermost) to 1.0 (innermost)
g.strokePath (arcVal, juce::PathStrokeType (glowW2, juce::PathStrokeType::curved, float layerWidth = glowW1 * (1.0f - t * 0.7f); // wide -> narrow
juce::PathStrokeType::rounded)); float layerAlpha = 0.03f + t * t * 0.35f; // exponential: faint -> bright
g.setColour (arcColour.withAlpha (0.3f)); g.setColour (arcColour.withAlpha (layerAlpha));
g.strokePath (arcVal, juce::PathStrokeType (glowW3, juce::PathStrokeType::curved, g.strokePath (arcVal, juce::PathStrokeType (layerWidth, juce::PathStrokeType::curved,
juce::PathStrokeType::rounded)); juce::PathStrokeType::rounded));
}
// Core arc // Core arc (full brightness)
g.setColour (arcColour); g.setColour (arcColour);
g.strokePath (arcVal, juce::PathStrokeType (arcW, juce::PathStrokeType::curved, g.strokePath (arcVal, juce::PathStrokeType (arcW, juce::PathStrokeType::curved,
juce::PathStrokeType::rounded)); juce::PathStrokeType::rounded));
// Hot center // Hot center (white-ish)
g.setColour (arcColour.brighter (0.6f).withAlpha (0.5f)); g.setColour (arcColour.brighter (0.6f).withAlpha (0.5f));
g.strokePath (arcVal, juce::PathStrokeType (hotW, juce::PathStrokeType::curved, g.strokePath (arcVal, juce::PathStrokeType (hotW, juce::PathStrokeType::curved,
juce::PathStrokeType::rounded)); juce::PathStrokeType::rounded));
@@ -191,37 +192,32 @@ void InstaDrumsLookAndFeel::drawRotarySlider (juce::Graphics& g, int x, int y, i
g.fillEllipse (cx - hlRadius, hlY - hlRadius * 0.6f, hlRadius * 2, hlRadius * 1.2f); g.fillEllipse (cx - hlRadius, hlY - hlRadius * 0.6f, hlRadius * 2, hlRadius * 1.2f);
} }
// 8. Pointer with scaled glow // 8. Pointer with subtle glow (half intensity)
{ {
juce::Path pointer;
float pointerLen = bodyRadius * 0.75f; float pointerLen = bodyRadius * 0.75f;
pointer.addRoundedRectangle (-ptrW * 0.5f, -pointerLen, ptrW, pointerLen * 0.55f, ptrW * 0.5f); // Smooth glow: 4 layers, half the width
pointer.applyTransform (juce::AffineTransform::rotation (angle).translated (cx, cy)); for (int i = 0; i < 4; ++i)
// Wide outer glow
g.setColour (pointerColour.withAlpha (0.1f));
{ {
juce::Path glow3; float t = (float) i / 3.0f;
float gw = ptrW * 3.5f; float gw = ptrW * (2.0f - t * 1.5f); // narrower spread
glow3.addRoundedRectangle (-gw, -pointerLen, gw * 2, pointerLen * 0.55f, ptrW * 1.5f); float alpha = 0.02f + t * t * 0.15f; // lower opacity
glow3.applyTransform (juce::AffineTransform::rotation (angle).translated (cx, cy));
g.fillPath (glow3);
}
// Medium glow juce::Path glowLayer;
g.setColour (pointerColour.withAlpha (0.25f)); glowLayer.addRoundedRectangle (-gw, -pointerLen, gw * 2, pointerLen * 0.55f, gw * 0.5f);
{ glowLayer.applyTransform (juce::AffineTransform::rotation (angle).translated (cx, cy));
juce::Path glow2; g.setColour (pointerColour.withAlpha (alpha));
float gw = ptrW * 2.0f; g.fillPath (glowLayer);
glow2.addRoundedRectangle (-gw, -pointerLen, gw * 2, pointerLen * 0.55f, ptrW);
glow2.applyTransform (juce::AffineTransform::rotation (angle).translated (cx, cy));
g.fillPath (glow2);
} }
// Core pointer // Core pointer
{
juce::Path pointer;
pointer.addRoundedRectangle (-ptrW * 0.5f, -pointerLen, ptrW, pointerLen * 0.55f, ptrW * 0.5f);
pointer.applyTransform (juce::AffineTransform::rotation (angle).translated (cx, cy));
g.setColour (pointerColour); g.setColour (pointerColour);
g.fillPath (pointer); g.fillPath (pointer);
}
// Hot center // Hot center
{ {
@@ -269,3 +265,100 @@ void InstaDrumsLookAndFeel::drawButtonBackground (juce::Graphics& g, juce::Butto
g.setColour (bgLight.withAlpha (shouldDrawButtonAsHighlighted ? 0.8f : 0.5f)); g.setColour (bgLight.withAlpha (shouldDrawButtonAsHighlighted ? 0.8f : 0.5f));
g.drawRoundedRectangle (bounds, 4.0f, 1.0f); g.drawRoundedRectangle (bounds, 4.0f, 1.0f);
} }
// ============================================================
// Toggle button — glowing radio dot
// ============================================================
void InstaDrumsLookAndFeel::drawToggleButton (juce::Graphics& g, juce::ToggleButton& button,
bool shouldDrawButtonAsHighlighted,
bool /*shouldDrawButtonAsDown*/)
{
auto bounds = button.getLocalBounds().toFloat();
float h = std::min (bounds.getHeight() * 0.6f, 14.0f);
float w = h * 1.8f;
float trackR = h * 0.5f;
// Center the switch in the component
float sx = bounds.getX() + (bounds.getWidth() - w) * 0.5f;
float sy = bounds.getCentreY() - h * 0.5f;
auto trackBounds = juce::Rectangle<float> (sx, sy, w, h);
bool isOn = button.getToggleState();
auto onColour = accent;
auto offColour = bgLight;
// Animated thumb position (stored as component property, lerps each frame)
float targetPos = isOn ? 1.0f : 0.0f;
float animPos = (float) button.getProperties().getWithDefault ("animPos", targetPos);
animPos += (targetPos - animPos) * 0.25f; // smooth lerp
if (std::abs (animPos - targetPos) < 0.01f) animPos = targetPos;
button.getProperties().set ("animPos", animPos);
// Trigger repaint if still animating
if (std::abs (animPos - targetPos) > 0.005f)
button.repaint();
float thumbR = h * 0.4f;
float thumbX = sx + trackR + animPos * (w - trackR * 2);
float thumbY = sy + h * 0.5f;
float glowIntensity = animPos; // 0 = off, 1 = full glow
// Track glow (intensity follows animation)
if (glowIntensity > 0.01f)
{
for (int i = 0; i < 3; ++i)
{
float t = (float) i / 2.0f;
float expand = (1.0f - t) * 1.5f;
float alpha = (0.04f + t * t * 0.1f) * glowIntensity;
g.setColour (onColour.withAlpha (alpha));
g.fillRoundedRectangle (trackBounds.expanded (expand), trackR + expand);
}
}
// Track background (blend between off/on colours)
{
juce::Colour offCol = offColour.withAlpha (0.3f);
juce::Colour onCol = onColour.withAlpha (0.35f);
juce::Colour trackCol = offCol.interpolatedWith (onCol, glowIntensity);
if (shouldDrawButtonAsHighlighted)
trackCol = trackCol.brighter (0.15f);
g.setColour (trackCol);
g.fillRoundedRectangle (trackBounds, trackR);
g.setColour (offColour.withAlpha (0.4f).interpolatedWith (onColour.withAlpha (0.5f), glowIntensity));
g.drawRoundedRectangle (trackBounds, trackR, 0.8f);
}
// Thumb glow (intensity follows animation)
if (glowIntensity > 0.01f)
{
for (int i = 0; i < 3; ++i)
{
float t = (float) i / 2.0f;
float r = thumbR * (1.5f - t * 0.5f);
float alpha = (0.05f + t * t * 0.12f) * glowIntensity;
g.setColour (onColour.withAlpha (alpha));
g.fillEllipse (thumbX - r, thumbY - r, r * 2, r * 2);
}
}
// Thumb circle (colour blends with animation)
{
juce::Colour thumbTopOff (0xff555566), thumbBotOff (0xff333344);
juce::Colour thumbTopOn = onColour.brighter (0.3f), thumbBotOn = onColour.darker (0.2f);
juce::ColourGradient thumbGrad (
thumbTopOff.interpolatedWith (thumbTopOn, glowIntensity), thumbX, thumbY - thumbR,
thumbBotOff.interpolatedWith (thumbBotOn, glowIntensity), thumbX, thumbY + thumbR, false);
g.setGradientFill (thumbGrad);
g.fillEllipse (thumbX - thumbR, thumbY - thumbR, thumbR * 2, thumbR * 2);
g.setColour (juce::Colour (0xff666677).withAlpha (0.5f).interpolatedWith (onColour.withAlpha (0.6f), glowIntensity));
g.drawEllipse (thumbX - thumbR, thumbY - thumbR, thumbR * 2, thumbR * 2, 0.8f);
float hlR = thumbR * 0.4f;
g.setColour (juce::Colours::white.withAlpha (0.1f + 0.15f * glowIntensity));
g.fillEllipse (thumbX - hlR, thumbY - thumbR * 0.6f - hlR * 0.3f, hlR * 2, hlR * 1.2f);
}
}

Fájl megtekintése

@@ -26,6 +26,10 @@ public:
bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsHighlighted,
bool shouldDrawButtonAsDown) override; bool shouldDrawButtonAsDown) override;
void drawToggleButton (juce::Graphics& g, juce::ToggleButton& button,
bool shouldDrawButtonAsHighlighted,
bool shouldDrawButtonAsDown) override;
// Custom fonts // Custom fonts
juce::Font getRegularFont (float height) const; juce::Font getRegularFont (float height) const;
juce::Font getMediumFont (float height) const; juce::Font getMediumFont (float height) const;

Fájl megtekintése

@@ -10,6 +10,13 @@ MasterPanel::MasterPanel()
setupKnob (tuneSlider, tuneLabel, "Tune", -12.0, 12.0, 0.0, 0.1); setupKnob (tuneSlider, tuneLabel, "Tune", -12.0, 12.0, 0.0, 0.1);
setupKnob (panSlider, panLabel, "Pan", -1.0, 1.0, 0.0, 0.01); setupKnob (panSlider, panLabel, "Pan", -1.0, 1.0, 0.0, 0.01);
limiterToggle.setToggleState (true, juce::dontSendNotification);
limiterToggle.setButtonText ("");
addAndMakeVisible (limiterToggle);
limiterLabel.setColour (juce::Label::textColourId, InstaDrumsLookAndFeel::accent);
limiterLabel.setJustificationType (juce::Justification::centredLeft);
addAndMakeVisible (limiterLabel);
addAndMakeVisible (vuMeter); addAndMakeVisible (vuMeter);
} }
@@ -45,13 +52,29 @@ void MasterPanel::resized()
float titleSize = std::max (12.0f, 16.0f * scale); float titleSize = std::max (12.0f, 16.0f * scale);
float labelSize = std::max (9.0f, 12.0f * scale); float labelSize = std::max (9.0f, 12.0f * scale);
int labelH = (int) (labelSize + 4); int labelH = (int) (labelSize + 4);
int toggleW = std::max (28, (int) (36 * scale));
int toggleH = std::max (14, (int) (16 * scale));
masterTitle.setFont (juce::FontOptions (titleSize, juce::Font::bold)); masterTitle.setFont (juce::FontOptions (titleSize, juce::Font::bold));
masterTitle.setBounds (area.removeFromLeft ((int) (65 * scale)).reduced (0, 2)); masterTitle.setBounds (area.removeFromLeft ((int) (65 * scale)).reduced (0, 2));
// VU meter on right
vuMeter.setBounds (area.removeFromRight ((int) (28 * scale)).reduced (0, 2)); vuMeter.setBounds (area.removeFromRight ((int) (28 * scale)).reduced (0, 2));
area.removeFromRight (4); area.removeFromRight (6);
// Limiter toggle + label
auto limArea = area.removeFromRight ((int) (90 * scale));
{
auto center = limArea.withSizeKeepingCentre (limArea.getWidth(), toggleH + 4);
auto row = center;
limiterToggle.setBounds (row.removeFromLeft (toggleW).withSizeKeepingCentre (toggleW, toggleH));
limiterLabel.setFont (juce::FontOptions (std::max (10.0f, 13.0f * scale), juce::Font::bold));
limiterLabel.setBounds (row);
}
area.removeFromRight (6);
// Master knobs
int knobW = area.getWidth() / 3; int knobW = area.getWidth() / 3;
juce::Slider* sliders[] = { &volumeSlider, &tuneSlider, &panSlider }; juce::Slider* sliders[] = { &volumeSlider, &tuneSlider, &panSlider };
juce::Label* labels[] = { &volumeLabel, &tuneLabel, &panLabel }; juce::Label* labels[] = { &volumeLabel, &tuneLabel, &panLabel };

Fájl megtekintése

@@ -13,6 +13,7 @@ public:
float getMasterVolume() const { return (float) volumeSlider.getValue(); } float getMasterVolume() const { return (float) volumeSlider.getValue(); }
float getMasterTune() const { return (float) tuneSlider.getValue(); } float getMasterTune() const { return (float) tuneSlider.getValue(); }
float getMasterPan() const { return (float) panSlider.getValue(); } float getMasterPan() const { return (float) panSlider.getValue(); }
bool isLimiterEnabled() const { return limiterToggle.getToggleState(); }
VuMeter& getVuMeter() { return vuMeter; } VuMeter& getVuMeter() { return vuMeter; }
@@ -20,6 +21,10 @@ private:
juce::Slider volumeSlider, tuneSlider, panSlider; juce::Slider volumeSlider, tuneSlider, panSlider;
juce::Label volumeLabel, tuneLabel, panLabel; juce::Label volumeLabel, tuneLabel, panLabel;
juce::Label masterTitle { {}, "MASTER" }; juce::Label masterTitle { {}, "MASTER" };
juce::ToggleButton limiterToggle;
juce::Label limiterLabel { {}, "LIMITER" };
VuMeter vuMeter; VuMeter vuMeter;
void setupKnob (juce::Slider& s, juce::Label& l, const juce::String& name, void setupKnob (juce::Slider& s, juce::Label& l, const juce::String& name,

Fájl megtekintése

@@ -142,7 +142,10 @@ void InstaDrumsEditor::selectPad (int index)
padComponents[i]->setSelected (i == index); padComponents[i]->setSelected (i == index);
if (index >= 0 && index < processor.getNumPads()) if (index >= 0 && index < processor.getNumPads())
{
sampleEditor.setPad (&processor.getPad (index)); sampleEditor.setPad (&processor.getPad (index));
fxPanel.setPad (&processor.getPad (index));
}
} }
void InstaDrumsEditor::paint (juce::Graphics& g) void InstaDrumsEditor::paint (juce::Graphics& g)
@@ -167,7 +170,7 @@ void InstaDrumsEditor::paint (juce::Graphics& g)
// Divider lines // Divider lines
auto bounds = getLocalBounds(); auto bounds = getLocalBounds();
int rightPanelX = (int) (bounds.getWidth() * 0.52f); int rightPanelX = (int) (bounds.getWidth() * 0.52f);
int masterH = std::max (44, (int) (60 * sc)); int masterH = std::max (50, (int) (65 * sc));
int bottomPanelY = bounds.getHeight() - masterH - 4; int bottomPanelY = bounds.getHeight() - masterH - 4;
g.setColour (InstaDrumsLookAndFeel::bgLight.withAlpha (0.4f)); g.setColour (InstaDrumsLookAndFeel::bgLight.withAlpha (0.4f));
@@ -199,7 +202,7 @@ void InstaDrumsEditor::resized()
loadSampleButton.setBounds (topBar.removeFromRight (btnW).reduced (2)); loadSampleButton.setBounds (topBar.removeFromRight (btnW).reduced (2));
// Bottom master bar // Bottom master bar
int masterH = std::max (44, (int) (60 * scale)); int masterH = std::max (50, (int) (65 * scale));
masterPanel.setBounds (area.removeFromBottom (masterH).reduced (4, 2)); masterPanel.setBounds (area.removeFromBottom (masterH).reduced (4, 2));
// Left panel: pad grid (~52% width) // Left panel: pad grid (~52% width)
@@ -242,22 +245,19 @@ void InstaDrumsEditor::timerCallback()
for (auto* pc : padComponents) for (auto* pc : padComponents)
pc->repaint(); pc->repaint();
// Sync FX panel knobs -> processor atomic params // Sync FX panel knobs -> selected pad's FX params
processor.compThreshold.store (fxPanel.getCompThreshold()); fxPanel.syncToPad();
processor.compRatio.store (fxPanel.getCompRatio());
processor.eqLo.store (fxPanel.getEqLo()); // Update per-pad compressor GR meter
processor.eqMid.store (fxPanel.getEqMid()); if (selectedPadIndex >= 0 && selectedPadIndex < processor.getNumPads())
processor.eqHi.store (fxPanel.getEqHi()); fxPanel.setCompGainReduction (processor.getPad (selectedPadIndex).compGainReduction.load());
processor.distDrive.store (fxPanel.getDistDrive());
processor.distMix.store (fxPanel.getDistMix());
processor.reverbSize.store (fxPanel.getReverbSize());
processor.reverbDecay.store (fxPanel.getReverbDecay());
// Sync master panel -> processor // Sync master panel -> processor
processor.masterVolume.store (masterPanel.getMasterVolume()); processor.masterVolume.store (masterPanel.getMasterVolume());
processor.masterPan.store (masterPanel.getMasterPan()); processor.masterPan.store (masterPanel.getMasterPan());
processor.masterTune.store (masterPanel.getMasterTune()); processor.masterTune.store (masterPanel.getMasterTune());
processor.outputLimiterEnabled.store (masterPanel.isLimiterEnabled());
// Update VU meter from processor // Update VU meter
masterPanel.getVuMeter().setLevel (processor.vuLevelL.load(), processor.vuLevelR.load()); masterPanel.getVuMeter().setLevel (processor.vuLevelL.load(), processor.vuLevelR.load());
} }

Fájl megtekintése

@@ -45,22 +45,7 @@ void InstaDrumsProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
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 // Per-pad FX is prepared in DrumPad::prepareToPlay()
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()
@@ -115,85 +100,8 @@ void InstaDrumsProcessor::applyMasterFx (juce::AudioBuffer<float>& buffer)
{ {
const int numSamples = buffer.getNumSamples(); const int numSamples = buffer.getNumSamples();
// --- Distortion (pre-EQ) --- // Per-pad FX is now in DrumPad::applyPadFx()
float drive = distDrive.load(); // Master chain: just Volume + Pan + Output Limiter
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 --- // --- Master Volume + Pan ---
float mVol = masterVolume.load(); float mVol = masterVolume.load();
@@ -212,6 +120,17 @@ void InstaDrumsProcessor::applyMasterFx (juce::AudioBuffer<float>& buffer)
buffer.applyGain (mVol); buffer.applyGain (mVol);
} }
// --- Output Limiter (brickwall at 0dB) ---
if (outputLimiterEnabled.load())
{
for (int ch = 0; ch < buffer.getNumChannels(); ++ch)
{
float* data = buffer.getWritePointer (ch);
for (int i = 0; i < numSamples; ++i)
data[i] = juce::jlimit (-1.0f, 1.0f, data[i]);
}
}
// --- VU Meter --- // --- VU Meter ---
float rmsL = 0.0f, rmsR = 0.0f; float rmsL = 0.0f, rmsR = 0.0f;
if (buffer.getNumChannels() >= 1) if (buffer.getNumChannels() >= 1)

Fájl megtekintése

@@ -52,16 +52,8 @@ public:
std::atomic<float> masterPan { 0.0f }; std::atomic<float> masterPan { 0.0f };
std::atomic<float> masterTune { 0.0f }; std::atomic<float> masterTune { 0.0f };
// FX params // Master bus
std::atomic<float> compThreshold { -12.0f }; std::atomic<bool> outputLimiterEnabled { true };
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) // VU meter levels (written by audio thread, read by GUI)
std::atomic<float> vuLevelL { 0.0f }; std::atomic<float> vuLevelL { 0.0f };
@@ -72,13 +64,6 @@ private:
int numActivePads = defaultNumPads; int numActivePads = defaultNumPads;
juce::AudioFormatManager formatManager; juce::AudioFormatManager formatManager;
// 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; double currentSampleRate = 44100.0;
void initializeDefaults(); void initializeDefaults();