Multi-output routing (7 stereo buses) + VU meter fix
- 7 stereo output buses: Main, Kick, Snare, HiHat, Toms, Cymbals, Perc - Each pad pre-assigned to appropriate bus (configurable via outputBus) - Pads route to assigned bus if active, fallback to Main if not - Master FX (limiter) applied to Main bus only - isBusesLayoutSupported: Main must be stereo, aux can be stereo or disabled - All buses enabled by default for REAPER multi-output detection - VU meter: switched from RMS to peak measurement (getMagnitude) - VU meter: sqrt scaling for better visibility on transient material - VU meter: removed distracting dB scale markers - VU meter: fast attack / medium release smoothing - README updated with multi-output routing section Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -28,10 +28,16 @@ Free, open-source VST3 drum sampler plugin built with JUCE.
|
|||||||
- Reverb (Size, Decay)
|
- Reverb (Size, Decay)
|
||||||
- Each FX toggleable with animated switches
|
- Each FX toggleable with animated switches
|
||||||
|
|
||||||
|
### Multi-Output Routing
|
||||||
|
- 7 stereo output buses: Main, Kick, Snare, HiHat, Toms, Cymbals, Perc
|
||||||
|
- Each pad pre-assigned to its bus (configurable)
|
||||||
|
- In REAPER: enable additional outputs via routing for separate track processing
|
||||||
|
- Pads with inactive buses automatically fall back to Main
|
||||||
|
|
||||||
### Master Bus
|
### Master Bus
|
||||||
- Master Volume, Tune, Pan
|
- Master Volume, Tune, Pan
|
||||||
- Output Limiter (0dB brickwall, toggleable)
|
- Output Limiter (0dB brickwall, toggleable)
|
||||||
- VU meter with peak hold
|
- Peak VU meter with hold indicator
|
||||||
|
|
||||||
### GUI
|
### GUI
|
||||||
- Dark modern UI inspired by hardware drum machines
|
- Dark modern UI inspired by hardware drum machines
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ public:
|
|||||||
float pitch = 0.0f;
|
float pitch = 0.0f;
|
||||||
bool oneShot = true;
|
bool oneShot = true;
|
||||||
int chokeGroup = -1;
|
int chokeGroup = -1;
|
||||||
|
int outputBus = 0; // 0 = Main, 1 = Kick, 2 = Snare, etc.
|
||||||
juce::Colour colour { 0xff00ff88 };
|
juce::Colour colour { 0xff00ff88 };
|
||||||
|
|
||||||
// ADSR
|
// ADSR
|
||||||
|
|||||||
@@ -3,7 +3,13 @@
|
|||||||
|
|
||||||
InstaDrumsProcessor::InstaDrumsProcessor()
|
InstaDrumsProcessor::InstaDrumsProcessor()
|
||||||
: AudioProcessor (BusesProperties()
|
: AudioProcessor (BusesProperties()
|
||||||
.withOutput ("Main", juce::AudioChannelSet::stereo(), true))
|
.withOutput ("Main", juce::AudioChannelSet::stereo(), true)
|
||||||
|
.withOutput ("Kick", juce::AudioChannelSet::stereo(), true)
|
||||||
|
.withOutput ("Snare", juce::AudioChannelSet::stereo(), true)
|
||||||
|
.withOutput ("HiHat", juce::AudioChannelSet::stereo(), true)
|
||||||
|
.withOutput ("Toms", juce::AudioChannelSet::stereo(), true)
|
||||||
|
.withOutput ("Cymbals", juce::AudioChannelSet::stereo(), true)
|
||||||
|
.withOutput ("Perc", juce::AudioChannelSet::stereo(), true))
|
||||||
{
|
{
|
||||||
formatManager.registerBasicFormats();
|
formatManager.registerBasicFormats();
|
||||||
initializeDefaults();
|
initializeDefaults();
|
||||||
@@ -11,30 +17,52 @@ InstaDrumsProcessor::InstaDrumsProcessor()
|
|||||||
|
|
||||||
InstaDrumsProcessor::~InstaDrumsProcessor() {}
|
InstaDrumsProcessor::~InstaDrumsProcessor() {}
|
||||||
|
|
||||||
|
const char* const InstaDrumsProcessor::outputBusNames[numOutputBuses] = {
|
||||||
|
"Main", "Kick", "Snare", "HiHat", "Toms", "Cymbals", "Perc"
|
||||||
|
};
|
||||||
|
|
||||||
|
bool InstaDrumsProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const
|
||||||
|
{
|
||||||
|
// Main output must be stereo
|
||||||
|
if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Aux outputs can be stereo or disabled
|
||||||
|
for (int i = 1; i < layouts.outputBuses.size(); ++i)
|
||||||
|
{
|
||||||
|
auto set = layouts.outputBuses[i];
|
||||||
|
if (! set.isDisabled() && set != juce::AudioChannelSet::stereo())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void InstaDrumsProcessor::initializeDefaults()
|
void InstaDrumsProcessor::initializeDefaults()
|
||||||
{
|
{
|
||||||
// GM Drum Map defaults for first 12 pads
|
// GM Drum Map defaults for first 12 pads
|
||||||
struct PadDefault { int note; const char* name; juce::uint32 colour; };
|
// Bus IDs: 0=Main, 1=Kick, 2=Snare, 3=HiHat, 4=Toms, 5=Cymbals, 6=Perc
|
||||||
|
struct PadDefault { int note; const char* name; juce::uint32 colour; int bus; };
|
||||||
static const PadDefault defaults[] = {
|
static const PadDefault defaults[] = {
|
||||||
{ 36, "Kick", 0xffff4444 }, // Red
|
{ 36, "Kick", 0xffff4444, 1 }, // -> Kick bus
|
||||||
{ 38, "Snare", 0xffff8844 }, // Orange
|
{ 38, "Snare", 0xffff8844, 2 }, // -> Snare bus
|
||||||
{ 42, "CH Hat", 0xffffff44 }, // Yellow
|
{ 42, "CH Hat", 0xffffff44, 3 }, // -> HiHat bus
|
||||||
{ 46, "OH Hat", 0xff88ff44 }, // Green
|
{ 46, "OH Hat", 0xff88ff44, 3 }, // -> HiHat bus
|
||||||
{ 45, "Low Tom", 0xff44ffaa }, // Teal
|
{ 45, "Low Tom", 0xff44ffaa, 4 }, // -> Toms bus
|
||||||
{ 48, "Mid Tom", 0xff44ddff }, // Cyan
|
{ 48, "Mid Tom", 0xff44ddff, 4 }, // -> Toms bus
|
||||||
{ 50, "Hi Tom", 0xff4488ff }, // Blue
|
{ 50, "Hi Tom", 0xff4488ff, 4 }, // -> Toms bus
|
||||||
{ 49, "Crash", 0xff8844ff }, // Purple
|
{ 49, "Crash", 0xff8844ff, 5 }, // -> Cymbals bus
|
||||||
{ 51, "Ride", 0xffcc44ff }, // Magenta
|
{ 51, "Ride", 0xffcc44ff, 5 }, // -> Cymbals bus
|
||||||
{ 39, "Clap", 0xffff44cc }, // Pink
|
{ 39, "Clap", 0xffff44cc, 6 }, // -> Perc bus
|
||||||
{ 56, "Cowbell", 0xffff8888 }, // Light red
|
{ 56, "Cowbell", 0xffff8888, 6 }, // -> Perc bus
|
||||||
{ 37, "Rimshot", 0xffaaaaff }, // Light blue
|
{ 37, "Rimshot", 0xffaaaaff, 6 }, // -> Perc bus
|
||||||
};
|
};
|
||||||
|
|
||||||
for (int i = 0; i < defaultNumPads && i < (int) std::size (defaults); ++i)
|
for (int i = 0; i < defaultNumPads && i < (int) std::size (defaults); ++i)
|
||||||
{
|
{
|
||||||
pads[i].midiNote = defaults[i].note;
|
pads[i].midiNote = defaults[i].note;
|
||||||
pads[i].name = defaults[i].name;
|
pads[i].name = defaults[i].name;
|
||||||
pads[i].colour = juce::Colour (defaults[i].colour);
|
pads[i].colour = juce::Colour (defaults[i].colour);
|
||||||
|
pads[i].outputBus = defaults[i].bus;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,12 +116,55 @@ void InstaDrumsProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render audio from all pads
|
const int numSamples = buffer.getNumSamples();
|
||||||
for (int i = 0; i < numActivePads; ++i)
|
const int totalChannels = buffer.getNumChannels();
|
||||||
pads[i].renderNextBlock (buffer, 0, buffer.getNumSamples());
|
|
||||||
|
|
||||||
// Apply master FX chain
|
// Render each pad to its assigned output bus (or Main if bus inactive)
|
||||||
applyMasterFx (buffer);
|
for (int i = 0; i < numActivePads; ++i)
|
||||||
|
{
|
||||||
|
int bus = pads[i].outputBus;
|
||||||
|
int chOffset = 0;
|
||||||
|
|
||||||
|
// Calculate channel offset for the target bus
|
||||||
|
// Each bus is 2 channels (stereo): bus 0 = ch 0-1, bus 1 = ch 2-3, etc.
|
||||||
|
if (bus > 0)
|
||||||
|
{
|
||||||
|
int targetOffset = bus * 2;
|
||||||
|
if (targetOffset + 1 < totalChannels)
|
||||||
|
chOffset = targetOffset; // Bus is active, use its channels
|
||||||
|
// else: bus not active, chOffset stays 0 (Main)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render pad into the correct channel pair
|
||||||
|
// We need a temporary view of the buffer at the right offset
|
||||||
|
if (chOffset == 0)
|
||||||
|
{
|
||||||
|
// Render to Main (channels 0-1)
|
||||||
|
pads[i].renderNextBlock (buffer, 0, numSamples);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Create a sub-buffer pointing to the target bus channels
|
||||||
|
float* channelPtrs[2] = {
|
||||||
|
buffer.getWritePointer (chOffset),
|
||||||
|
buffer.getWritePointer (chOffset + 1)
|
||||||
|
};
|
||||||
|
juce::AudioBuffer<float> busBuffer (channelPtrs, 2, numSamples);
|
||||||
|
pads[i].renderNextBlock (busBuffer, 0, numSamples);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply master FX to Main bus only (channels 0-1)
|
||||||
|
if (totalChannels >= 2)
|
||||||
|
{
|
||||||
|
float* mainPtrs[2] = { buffer.getWritePointer (0), buffer.getWritePointer (1) };
|
||||||
|
juce::AudioBuffer<float> mainBuf (mainPtrs, 2, numSamples);
|
||||||
|
applyMasterFx (mainBuf);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
applyMasterFx (buffer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstaDrumsProcessor::applyMasterFx (juce::AudioBuffer<float>& buffer)
|
void InstaDrumsProcessor::applyMasterFx (juce::AudioBuffer<float>& buffer)
|
||||||
@@ -131,16 +202,15 @@ void InstaDrumsProcessor::applyMasterFx (juce::AudioBuffer<float>& buffer)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- VU Meter ---
|
// --- VU Meter (peak level) ---
|
||||||
float rmsL = 0.0f, rmsR = 0.0f;
|
float peakL = 0.0f, peakR = 0.0f;
|
||||||
if (buffer.getNumChannels() >= 1)
|
if (buffer.getNumChannels() >= 1)
|
||||||
rmsL = buffer.getRMSLevel (0, 0, numSamples);
|
peakL = buffer.getMagnitude (0, 0, numSamples);
|
||||||
if (buffer.getNumChannels() >= 2)
|
if (buffer.getNumChannels() >= 2)
|
||||||
rmsR = buffer.getRMSLevel (1, 0, numSamples);
|
peakR = buffer.getMagnitude (1, 0, numSamples);
|
||||||
|
|
||||||
// Smooth VU (simple exponential)
|
vuLevelL.store (peakL);
|
||||||
vuLevelL.store (vuLevelL.load() * 0.8f + rmsL * 0.2f);
|
vuLevelR.store (peakR);
|
||||||
vuLevelR.store (vuLevelR.load() * 0.8f + rmsR * 0.2f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DrumPad* InstaDrumsProcessor::findPadForNote (int midiNote)
|
DrumPad* InstaDrumsProcessor::findPadForNote (int midiNote)
|
||||||
|
|||||||
@@ -21,8 +21,13 @@ public:
|
|||||||
const juce::String getName() const override { return JucePlugin_Name; }
|
const juce::String getName() const override { return JucePlugin_Name; }
|
||||||
bool acceptsMidi() const override { return true; }
|
bool acceptsMidi() const override { return true; }
|
||||||
bool producesMidi() const override { return true; }
|
bool producesMidi() const override { return true; }
|
||||||
|
bool isBusesLayoutSupported (const BusesLayout& layouts) const override;
|
||||||
double getTailLengthSeconds() const override { return 0.0; }
|
double getTailLengthSeconds() const override { return 0.0; }
|
||||||
|
|
||||||
|
// Output bus names
|
||||||
|
static constexpr int numOutputBuses = 7;
|
||||||
|
static const char* const outputBusNames[numOutputBuses];
|
||||||
|
|
||||||
int getNumPrograms() override { return 1; }
|
int getNumPrograms() override { return 1; }
|
||||||
int getCurrentProgram() override { return 0; }
|
int getCurrentProgram() override { return 0; }
|
||||||
void setCurrentProgram (int) override {}
|
void setCurrentProgram (int) override {}
|
||||||
|
|||||||
@@ -12,8 +12,9 @@ public:
|
|||||||
if (right > peakR) peakR = right;
|
if (right > peakR) peakR = right;
|
||||||
else peakR *= 0.995f;
|
else peakR *= 0.995f;
|
||||||
|
|
||||||
levelL = left;
|
// Smooth level (fast attack, medium release)
|
||||||
levelR = right;
|
levelL = std::max (left, levelL * 0.85f);
|
||||||
|
levelR = std::max (right, levelR * 0.85f);
|
||||||
repaint();
|
repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,19 +29,6 @@ public:
|
|||||||
|
|
||||||
drawBar (g, leftBar, levelL, peakL);
|
drawBar (g, leftBar, levelL, peakL);
|
||||||
drawBar (g, rightBar, levelR, peakR);
|
drawBar (g, rightBar, levelR, peakR);
|
||||||
|
|
||||||
// Scale markers
|
|
||||||
g.setColour (juce::Colour (0x44ffffff));
|
|
||||||
g.setFont (juce::FontOptions (9.0f));
|
|
||||||
float totalH = getLocalBounds().toFloat().getHeight();
|
|
||||||
// dB markers: 0dB = top, -6, -12, -24, -48
|
|
||||||
float dbLevels[] = { 1.0f, 0.5f, 0.25f, 0.063f, 0.004f };
|
|
||||||
const char* dbLabels[] = { "0", "-6", "-12", "-24", "-48" };
|
|
||||||
for (int i = 0; i < 5; ++i)
|
|
||||||
{
|
|
||||||
float yPos = (1.0f - dbLevels[i]) * totalH;
|
|
||||||
g.drawHorizontalLine ((int) yPos, 0.0f, (float) getWidth());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -53,14 +41,15 @@ private:
|
|||||||
g.setColour (juce::Colour (0xff111122));
|
g.setColour (juce::Colour (0xff111122));
|
||||||
g.fillRoundedRectangle (bar, 2.0f);
|
g.fillRoundedRectangle (bar, 2.0f);
|
||||||
|
|
||||||
float clampedLevel = juce::jlimit (0.0f, 1.0f, level);
|
// Scale level to dB-ish display (boost low levels for visibility)
|
||||||
float h = bar.getHeight() * clampedLevel;
|
float displayLevel = std::pow (juce::jlimit (0.0f, 1.0f, level), 0.5f);
|
||||||
|
float h = bar.getHeight() * displayLevel;
|
||||||
auto filled = bar.withTop (bar.getBottom() - h);
|
auto filled = bar.withTop (bar.getBottom() - h);
|
||||||
|
|
||||||
// Segmented colour
|
// Segmented colour
|
||||||
if (clampedLevel < 0.6f)
|
if (displayLevel < 0.6f)
|
||||||
g.setColour (juce::Colour (0xff00cc44));
|
g.setColour (juce::Colour (0xff00cc44));
|
||||||
else if (clampedLevel < 0.85f)
|
else if (displayLevel < 0.85f)
|
||||||
g.setColour (juce::Colour (0xffcccc00));
|
g.setColour (juce::Colour (0xffcccc00));
|
||||||
else
|
else
|
||||||
g.setColour (juce::Colour (0xffff3333));
|
g.setColour (juce::Colour (0xffff3333));
|
||||||
@@ -68,10 +57,10 @@ private:
|
|||||||
g.fillRoundedRectangle (filled, 2.0f);
|
g.fillRoundedRectangle (filled, 2.0f);
|
||||||
|
|
||||||
// Peak hold line
|
// Peak hold line
|
||||||
float clampedPeak = juce::jlimit (0.0f, 1.0f, peak);
|
float displayPeak = std::pow (juce::jlimit (0.0f, 1.0f, peak), 0.5f);
|
||||||
if (clampedPeak > 0.01f)
|
if (displayPeak > 0.01f)
|
||||||
{
|
{
|
||||||
float peakY = bar.getBottom() - bar.getHeight() * clampedPeak;
|
float peakY = bar.getBottom() - bar.getHeight() * displayPeak;
|
||||||
g.setColour (juce::Colours::white.withAlpha (0.8f));
|
g.setColour (juce::Colours::white.withAlpha (0.8f));
|
||||||
g.fillRect (bar.getX(), peakY, bar.getWidth(), 1.5f);
|
g.fillRect (bar.getX(), peakY, bar.getWidth(), 1.5f);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user