Initial commit: InstaDrums VST3 drum sampler plugin

- 12-pad drum sampler with 4x3 grid (expandable by 4)
- Velocity layers with round-robin (Salamander-style filename parsing)
- Rhythm Engine-style GUI: pad grid (left), sample editor (right top),
  FX panel (right bottom), master panel (bottom)
- Waveform thumbnails on pads + large waveform in sample editor
- ADSR envelope, pitch, pan per pad
- Drag & drop sample/folder loading
- Kit save/load (.drumkit XML presets)
- Load Folder with smart name matching (kick, snare, hihat, etc.)
- Choke groups, one-shot/polyphonic mode
- Dark modern LookAndFeel with neon accent colors
- Built with JUCE framework, CMake, MSVC 2022

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

166
Source/PadComponent.cpp Normal file
Fájl megtekintése

@@ -0,0 +1,166 @@
#include "PadComponent.h"
#include "LookAndFeel.h"
PadComponent::PadComponent (DrumPad& pad, std::function<void(int, const juce::File&)> loadCallback, int padIndex)
: drumPad (pad), onLoadSample (std::move (loadCallback)), index (padIndex)
{
}
void PadComponent::paint (juce::Graphics& g)
{
auto bounds = getLocalBounds().toFloat().reduced (2.0f);
float cornerSize = 6.0f;
// Background
float alpha = isPressed ? 0.85f : (isDragOver ? 0.65f : (selected ? 0.55f : 0.3f));
g.setColour (drumPad.colour.withAlpha (alpha));
g.fillRoundedRectangle (bounds, cornerSize);
// Border — selected = bright accent, normal = pad colour
if (selected)
{
g.setColour (juce::Colour (0xff00aaff)); // blue selection
g.drawRoundedRectangle (bounds, cornerSize, 2.5f);
}
else
{
g.setColour (drumPad.colour.withAlpha (0.6f));
g.drawRoundedRectangle (bounds, cornerSize, 1.0f);
}
// Pad number (top-left, small)
g.setColour (InstaDrumsLookAndFeel::textSecondary);
g.setFont (juce::Font (juce::FontOptions (9.0f, juce::Font::bold)));
g.drawText (juce::String (index + 1), bounds.reduced (4, 3), juce::Justification::topLeft);
// Waveform thumbnail (center area)
if (drumPad.hasSample())
{
auto waveArea = bounds.reduced (4, 16);
drawWaveformThumbnail (g, waveArea);
}
// Pad name (bottom)
g.setColour (InstaDrumsLookAndFeel::textPrimary);
g.setFont (juce::Font (juce::FontOptions (10.0f, juce::Font::bold)));
g.drawText (drumPad.name, bounds.reduced (4, 2), juce::Justification::centredBottom);
// Playing flash
if (drumPad.isPlaying())
{
g.setColour (juce::Colours::white.withAlpha (0.12f));
g.fillRoundedRectangle (bounds, cornerSize);
}
}
void PadComponent::drawWaveformThumbnail (juce::Graphics& g, juce::Rectangle<float> area)
{
auto& buf = drumPad.getSampleBuffer();
if (buf.getNumSamples() == 0) return;
const float* data = buf.getReadPointer (0);
const int numSamples = buf.getNumSamples();
const float w = area.getWidth();
const float h = area.getHeight();
const float midY = area.getCentreY();
juce::Path path;
int blockSize = std::max (1, numSamples / (int) w);
for (int x = 0; x < (int) w; ++x)
{
int si = (int) ((float) x / w * numSamples);
si = juce::jlimit (0, numSamples - 1, si);
float maxVal = 0.0f;
for (int j = 0; j < blockSize && (si + j) < numSamples; ++j)
maxVal = std::max (maxVal, std::abs (data[si + j]));
float topY = midY - maxVal * (h * 0.45f);
float botY = midY + maxVal * (h * 0.45f);
if (x == 0)
path.startNewSubPath (area.getX() + (float) x, topY);
path.lineTo (area.getX() + (float) x, topY);
}
// Mirror bottom
for (int x = (int) w - 1; x >= 0; --x)
{
int si = (int) ((float) x / w * numSamples);
si = juce::jlimit (0, numSamples - 1, si);
float maxVal = 0.0f;
for (int j = 0; j < blockSize && (si + j) < numSamples; ++j)
maxVal = std::max (maxVal, std::abs (data[si + j]));
float botY = midY + maxVal * (h * 0.45f);
path.lineTo (area.getX() + (float) x, botY);
}
path.closeSubPath();
// Greenish waveform tint
g.setColour (juce::Colour (0xff44cc88).withAlpha (0.4f));
g.fillPath (path);
g.setColour (juce::Colour (0xff44cc88).withAlpha (0.7f));
g.strokePath (path, juce::PathStrokeType (0.8f));
}
void PadComponent::resized() {}
void PadComponent::mouseDown (const juce::MouseEvent& event)
{
if (event.mods.isRightButtonDown())
return;
// Select this pad
if (onSelected)
onSelected (index);
isPressed = true;
drumPad.trigger (1.0f);
repaint();
}
void PadComponent::mouseUp (const juce::MouseEvent& event)
{
if (event.mods.isRightButtonDown())
return;
isPressed = false;
if (! drumPad.oneShot)
drumPad.stop();
repaint();
}
bool PadComponent::isInterestedInFileDrag (const juce::StringArray& files)
{
for (auto& f : files)
{
juce::File file (f);
if (file.isDirectory()) return true;
auto ext = file.getFileExtension().toLowerCase();
if (ext == ".wav" || ext == ".aiff" || ext == ".aif" || ext == ".flac"
|| ext == ".ogg" || ext == ".mp3")
return true;
}
return false;
}
void PadComponent::filesDropped (const juce::StringArray& files, int, int)
{
isDragOver = false;
if (! files.isEmpty() && onLoadSample)
onLoadSample (index, juce::File (files[0]));
repaint();
}
void PadComponent::fileDragEnter (const juce::StringArray&, int, int)
{
isDragOver = true;
repaint();
}
void PadComponent::fileDragExit (const juce::StringArray&)
{
isDragOver = false;
repaint();
}