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

7
.gitignore vendored Normal file
Fájl megtekintése

@@ -0,0 +1,7 @@
build/
Samples/
*.user
*.suo
.vs/
CMakeSettings.json
out/

58
CMakeLists.txt Normal file
Fájl megtekintése

@@ -0,0 +1,58 @@
cmake_minimum_required(VERSION 3.22)
project(InstaDrums VERSION 1.0.0)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../JUCE ${CMAKE_CURRENT_BINARY_DIR}/JUCE)
juce_add_plugin(InstaDrums
COMPANY_NAME "InstaDrums"
IS_SYNTH TRUE
NEEDS_MIDI_INPUT TRUE
NEEDS_MIDI_OUTPUT TRUE
PLUGIN_MANUFACTURER_CODE Inst
PLUGIN_CODE Idrm
FORMATS VST3 Standalone
PRODUCT_NAME "InstaDrums"
COPY_PLUGIN_AFTER_BUILD FALSE
)
juce_generate_juce_header(InstaDrums)
target_sources(InstaDrums
PRIVATE
Source/PluginProcessor.cpp
Source/PluginEditor.cpp
Source/DrumPad.cpp
Source/PadComponent.cpp
Source/LookAndFeel.cpp
Source/WaveformDisplay.cpp
Source/SampleEditorPanel.cpp
Source/FxPanel.cpp
Source/MasterPanel.cpp
)
target_compile_definitions(InstaDrums
PUBLIC
JUCE_WEB_BROWSER=0
JUCE_USE_CURL=0
JUCE_VST3_CAN_REPLACE_VST2=0
)
target_link_libraries(InstaDrums
PRIVATE
juce::juce_audio_basics
juce::juce_audio_devices
juce::juce_audio_formats
juce::juce_audio_processors
juce::juce_audio_utils
juce::juce_core
juce::juce_dsp
juce::juce_graphics
juce::juce_gui_basics
juce::juce_gui_extra
PUBLIC
juce::juce_recommended_config_flags
juce::juce_recommended_warning_flags
)

385
Source/DrumPad.cpp Normal file
Fájl megtekintése

@@ -0,0 +1,385 @@
#include "DrumPad.h"
DrumPad::DrumPad() {}
DrumPad::~DrumPad() {}
void DrumPad::prepareToPlay (double sr, int /*samplesPerBlock*/)
{
sampleRate = sr;
}
void DrumPad::releaseResources()
{
playing = false;
activeSample = nullptr;
envStage = EnvelopeStage::Idle;
envLevel = 0.0f;
}
// ============================================================
// Velocity tag parsing from Salamander-style filenames
// Tags: Ghost, PP, P, MP, F, FF
// ============================================================
float DrumPad::velocityTagToLow (const juce::String& tag)
{
if (tag == "Ghost") return 0.0f;
if (tag == "PP") return 0.05f;
if (tag == "P") return 0.15f;
if (tag == "MP") return 0.35f;
if (tag == "F") return 0.55f;
if (tag == "FF") return 0.80f;
return 0.0f;
}
float DrumPad::velocityTagToHigh (const juce::String& tag)
{
if (tag == "Ghost") return 0.05f;
if (tag == "PP") return 0.15f;
if (tag == "P") return 0.35f;
if (tag == "MP") return 0.55f;
if (tag == "F") return 0.80f;
if (tag == "FF") return 1.0f;
return 1.0f;
}
// ============================================================
// Single sample loading (one layer, full velocity range)
// ============================================================
void DrumPad::loadSample (const juce::File& file, juce::AudioFormatManager& formatManager)
{
std::unique_ptr<juce::AudioFormatReader> reader (formatManager.createReaderFor (file));
if (reader == nullptr) return;
layers.clear();
activeSample = nullptr;
auto* layer = new VelocityLayer();
layer->velocityLow = 0.0f;
layer->velocityHigh = 1.0f;
auto* sample = new Sample();
sample->buffer.setSize ((int) reader->numChannels, (int) reader->lengthInSamples);
reader->read (&sample->buffer, 0, (int) reader->lengthInSamples, 0, true, true);
sample->sampleRate = reader->sampleRate;
sample->file = file;
layer->samples.add (sample);
layers.add (layer);
loadedFileName = file.getFileName();
loadedFile = file;
readPosition = 0.0;
playing = false;
}
// ============================================================
// Velocity layer loading from folder
// Expects filenames like: snare_OH_FF_1.flac, snare_OH_Ghost_3.flac
// Groups by velocity tag, each group becomes round-robin variations
// ============================================================
void DrumPad::loadLayersFromFolder (const juce::File& folder, juce::AudioFormatManager& formatManager)
{
if (! folder.isDirectory()) return;
layers.clear();
activeSample = nullptr;
// Collect audio files
juce::Array<juce::File> audioFiles;
for (auto& f : folder.findChildFiles (juce::File::findFiles, false))
{
auto ext = f.getFileExtension().toLowerCase();
if (ext == ".wav" || ext == ".aiff" || ext == ".aif" || ext == ".flac"
|| ext == ".ogg" || ext == ".mp3")
audioFiles.add (f);
}
if (audioFiles.isEmpty()) return;
// Known velocity tags to look for in filenames
static const juce::StringArray velocityTags = { "Ghost", "PP", "P", "MP", "F", "FF" };
// Group files by velocity tag
std::map<juce::String, juce::Array<juce::File>> groups;
for (auto& file : audioFiles)
{
auto nameNoExt = file.getFileNameWithoutExtension();
// Split by underscore and look for velocity tags
juce::String foundTag = "FF"; // default if no tag found
auto parts = juce::StringArray::fromTokens (nameNoExt, "_", "");
for (auto& part : parts)
{
if (velocityTags.contains (part))
{
foundTag = part;
break;
}
}
groups[foundTag].add (file);
}
// If only one group found with no velocity differentiation, treat as single layer
if (groups.size() == 1 && groups.begin()->first == "FF")
{
// All files are round-robin for a single full-velocity layer
auto* layer = new VelocityLayer();
layer->velocityLow = 0.0f;
layer->velocityHigh = 1.0f;
for (auto& file : groups.begin()->second)
{
std::unique_ptr<juce::AudioFormatReader> reader (formatManager.createReaderFor (file));
if (reader != nullptr)
{
auto* sample = new Sample();
sample->buffer.setSize ((int) reader->numChannels, (int) reader->lengthInSamples);
reader->read (&sample->buffer, 0, (int) reader->lengthInSamples, 0, true, true);
sample->sampleRate = reader->sampleRate;
sample->file = file;
layer->samples.add (sample);
}
}
if (! layer->samples.isEmpty())
layers.add (layer);
}
else
{
// Multiple velocity groups — create one layer per group
for (auto& [tag, files] : groups)
{
auto* layer = new VelocityLayer();
layer->velocityLow = velocityTagToLow (tag);
layer->velocityHigh = velocityTagToHigh (tag);
files.sort();
for (auto& file : files)
{
std::unique_ptr<juce::AudioFormatReader> reader (formatManager.createReaderFor (file));
if (reader != nullptr)
{
auto* sample = new Sample();
sample->buffer.setSize ((int) reader->numChannels, (int) reader->lengthInSamples);
reader->read (&sample->buffer, 0, (int) reader->lengthInSamples, 0, true, true);
sample->sampleRate = reader->sampleRate;
sample->file = file;
layer->samples.add (sample);
}
}
if (! layer->samples.isEmpty())
layers.add (layer);
}
}
// Sort layers by velocity range
std::sort (layers.begin(), layers.end(),
[] (const VelocityLayer* a, const VelocityLayer* b)
{ return a->velocityLow < b->velocityLow; });
loadedFileName = folder.getFileName() + " (" + juce::String (layers.size()) + " layers)";
loadedFile = folder;
readPosition = 0.0;
playing = false;
}
// ============================================================
// State queries
// ============================================================
bool DrumPad::hasSample() const
{
for (auto* layer : layers)
if (! layer->samples.isEmpty())
return true;
return false;
}
const juce::AudioBuffer<float>& DrumPad::getSampleBuffer() const
{
if (activeSample != nullptr)
return activeSample->buffer;
// Return first available sample buffer for waveform display
for (auto* layer : layers)
if (! layer->samples.isEmpty())
return layer->samples[0]->buffer;
return emptyBuffer;
}
// ============================================================
// Velocity layer selection
// ============================================================
DrumPad::VelocityLayer* DrumPad::findLayerForVelocity (float velocity)
{
// Find the layer whose range contains this velocity
for (auto* layer : layers)
if (velocity >= layer->velocityLow && velocity <= layer->velocityHigh)
return layer;
// Fallback: closest layer
VelocityLayer* closest = nullptr;
float minDist = 2.0f;
for (auto* layer : layers)
{
float mid = (layer->velocityLow + layer->velocityHigh) * 0.5f;
float dist = std::abs (velocity - mid);
if (dist < minDist)
{
minDist = dist;
closest = layer;
}
}
return closest;
}
// ============================================================
// Trigger / Stop
// ============================================================
void DrumPad::trigger (float velocity)
{
if (! hasSample()) return;
auto* layer = findLayerForVelocity (velocity);
if (layer == nullptr) return;
activeSample = layer->getNextSample();
if (activeSample == nullptr) return;
currentVelocity = velocity;
readPosition = 0.0;
envStage = EnvelopeStage::Attack;
envLevel = 0.0f;
playing = true;
}
void DrumPad::stop()
{
if (playing)
envStage = EnvelopeStage::Release;
}
// ============================================================
// ADSR Envelope
// ============================================================
void DrumPad::advanceEnvelope()
{
float attackSamples = std::max (1.0f, attack * (float) sampleRate);
float decaySamples = std::max (1.0f, decay * (float) sampleRate);
float releaseSamples = std::max (1.0f, release * (float) sampleRate);
switch (envStage)
{
case EnvelopeStage::Attack:
envLevel += 1.0f / attackSamples;
if (envLevel >= 1.0f)
{
envLevel = 1.0f;
envStage = EnvelopeStage::Decay;
}
break;
case EnvelopeStage::Decay:
envLevel -= (1.0f - sustain) / decaySamples;
if (envLevel <= sustain)
{
envLevel = sustain;
envStage = EnvelopeStage::Sustain;
}
break;
case EnvelopeStage::Sustain:
envLevel = sustain;
break;
case EnvelopeStage::Release:
envLevel -= envLevel / releaseSamples;
if (envLevel < 0.001f)
{
envLevel = 0.0f;
envStage = EnvelopeStage::Idle;
playing = false;
}
break;
case EnvelopeStage::Idle:
envLevel = 0.0f;
break;
}
}
// ============================================================
// Audio rendering
// ============================================================
void DrumPad::renderNextBlock (juce::AudioBuffer<float>& outputBuffer, int startSample, int numSamples)
{
if (! playing || activeSample == nullptr)
return;
const auto& sampleBuffer = activeSample->buffer;
const int sampleLength = sampleBuffer.getNumSamples();
const int srcChannels = sampleBuffer.getNumChannels();
const double sourceSR = activeSample->sampleRate;
double pitchRatio = std::pow (2.0, (double) pitch / 12.0) * (sourceSR / sampleRate);
// Constant power pan law
float panPos = (pan + 1.0f) * 0.5f;
float leftGain = std::cos (panPos * juce::MathConstants<float>::halfPi);
float rightGain = std::sin (panPos * juce::MathConstants<float>::halfPi);
for (int i = 0; i < numSamples; ++i)
{
if (! playing) break;
int pos0 = (int) readPosition;
if (pos0 >= sampleLength)
{
if (oneShot)
{
playing = false;
envStage = EnvelopeStage::Idle;
envLevel = 0.0f;
activeSample = nullptr;
break;
}
else
{
envStage = EnvelopeStage::Release;
}
}
if (pos0 < sampleLength)
{
advanceEnvelope();
float gain = volume * currentVelocity * envLevel;
int pos1 = std::min (pos0 + 1, sampleLength - 1);
float frac = (float) (readPosition - (double) pos0);
for (int ch = 0; ch < outputBuffer.getNumChannels(); ++ch)
{
int srcCh = std::min (ch, srcChannels - 1);
float s0 = sampleBuffer.getSample (srcCh, pos0);
float s1 = sampleBuffer.getSample (srcCh, pos1);
float sampleVal = s0 + frac * (s1 - s0);
float channelGain = (ch == 0) ? leftGain : rightGain;
outputBuffer.addSample (ch, startSample + i, sampleVal * gain * channelGain);
}
}
readPosition += pitchRatio;
}
}

106
Source/DrumPad.h Normal file
Fájl megtekintése

@@ -0,0 +1,106 @@
#pragma once
#include <JuceHeader.h>
class DrumPad
{
public:
// A single sample with its audio data and source sample rate
struct Sample
{
juce::AudioBuffer<float> buffer;
double sampleRate = 44100.0;
juce::File file;
};
// A velocity layer: velocity range + multiple round-robin samples
struct VelocityLayer
{
float velocityLow = 0.0f; // 0.0 - 1.0
float velocityHigh = 1.0f;
juce::OwnedArray<Sample> samples; // round-robin variations
int nextRoundRobin = 0;
Sample* getNextSample()
{
if (samples.isEmpty()) return nullptr;
auto* s = samples[nextRoundRobin % samples.size()];
nextRoundRobin = (nextRoundRobin + 1) % samples.size();
return s;
}
};
DrumPad();
~DrumPad();
void prepareToPlay (double sampleRate, int samplesPerBlock);
void releaseResources();
// Single sample loading (backwards compatible)
void loadSample (const juce::File& file, juce::AudioFormatManager& formatManager);
// Velocity layer loading from a folder
void loadLayersFromFolder (const juce::File& folder, juce::AudioFormatManager& formatManager);
bool hasSample() const;
void trigger (float velocity = 1.0f);
void stop();
void renderNextBlock (juce::AudioBuffer<float>& outputBuffer, int startSample, int numSamples);
// Pad properties
juce::String name;
int midiNote = 36;
float volume = 1.0f;
float pan = 0.0f;
float pitch = 0.0f;
bool oneShot = true;
int chokeGroup = -1;
juce::Colour colour { 0xff00ff88 };
// ADSR
float attack = 0.001f;
float decay = 0.1f;
float sustain = 1.0f;
float release = 0.05f;
// State
bool isPlaying() const { return playing; }
juce::String getLoadedFileName() const { return loadedFileName; }
juce::File getLoadedFile() const { return loadedFile; }
int getNumLayers() const { return layers.size(); }
const juce::AudioBuffer<float>& getSampleBuffer() const;
private:
// Velocity layers (sorted by velocity range)
juce::OwnedArray<VelocityLayer> layers;
// Currently playing sample reference
Sample* activeSample = nullptr;
// Fallback empty buffer for getSampleBuffer when nothing loaded
juce::AudioBuffer<float> emptyBuffer;
double sampleRate = 44100.0;
double readPosition = 0.0;
bool playing = false;
float currentVelocity = 1.0f;
// ADSR state
enum class EnvelopeStage { Idle, Attack, Decay, Sustain, Release };
EnvelopeStage envStage = EnvelopeStage::Idle;
float envLevel = 0.0f;
juce::String loadedFileName;
juce::File loadedFile;
void advanceEnvelope();
VelocityLayer* findLayerForVelocity (float velocity);
// Parse velocity tag from filename (e.g. "snare_OH_FF_1" -> FF)
static float velocityTagToLow (const juce::String& tag);
static float velocityTagToHigh (const juce::String& tag);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DrumPad)
};

116
Source/FxPanel.cpp Normal file
Fájl megtekintése

@@ -0,0 +1,116 @@
#include "FxPanel.h"
#include "LookAndFeel.h"
FxPanel::FxPanel()
{
setupTitle (compTitle, "COMPRESSOR");
setupTitle (eqTitle, "EQ");
setupTitle (distTitle, "DISTORTION");
setupTitle (reverbTitle, "REVERB");
setupKnob (compThreshSlider, compThreshLabel, "Threshold", -60.0, 0.0, -12.0, 0.5);
setupKnob (compRatioSlider, compRatioLabel, "Ratio", 1.0, 20.0, 4.0, 0.1);
setupKnob (eqLoSlider, eqLoLabel, "Lo", -12.0, 12.0, 0.0, 0.1);
setupKnob (eqMidSlider, eqMidLabel, "Mid", -12.0, 12.0, 0.0, 0.1);
setupKnob (eqHiSlider, eqHiLabel, "Hi", -12.0, 12.0, 0.0, 0.1);
setupKnob (distDriveSlider, distDriveLabel, "Drive", 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 (reverbDecaySlider, reverbDecayLabel, "Decay", 0.0, 1.0, 0.5, 0.01);
}
void FxPanel::setupKnob (juce::Slider& s, juce::Label& l, const juce::String& name,
double min, double max, double val, double step)
{
s.setSliderStyle (juce::Slider::RotaryHorizontalVerticalDrag);
s.setTextBoxStyle (juce::Slider::NoTextBox, false, 0, 0);
s.setRange (min, max, step);
s.setValue (val, juce::dontSendNotification);
addAndMakeVisible (s);
l.setText (name, juce::dontSendNotification);
l.setFont (juce::FontOptions (9.0f));
l.setColour (juce::Label::textColourId, InstaDrumsLookAndFeel::textSecondary);
l.setJustificationType (juce::Justification::centred);
addAndMakeVisible (l);
}
void FxPanel::setupTitle (juce::Label& l, const juce::String& text)
{
l.setText (text, juce::dontSendNotification);
l.setFont (juce::FontOptions (10.0f, juce::Font::bold));
l.setColour (juce::Label::textColourId, InstaDrumsLookAndFeel::accent);
l.setJustificationType (juce::Justification::centredLeft);
addAndMakeVisible (l);
}
void FxPanel::paint (juce::Graphics& g)
{
auto bounds = getLocalBounds().toFloat();
g.setColour (InstaDrumsLookAndFeel::bgMedium);
g.fillRoundedRectangle (bounds, 6.0f);
g.setColour (InstaDrumsLookAndFeel::bgLight.withAlpha (0.5f));
g.drawRoundedRectangle (bounds, 6.0f, 1.0f);
// "FX" header
g.setColour (InstaDrumsLookAndFeel::textSecondary);
g.setFont (juce::FontOptions (14.0f, juce::Font::bold));
g.drawText ("FX", bounds.reduced (6, 4).removeFromTop (18), juce::Justification::centredLeft);
}
void FxPanel::resized()
{
auto area = getLocalBounds().reduced (6);
area.removeFromTop (20); // FX header
int halfW = area.getWidth() / 2;
int rowH = area.getHeight() / 2;
// Top row: Compressor | EQ
auto topRow = area.removeFromTop (rowH);
{
auto compArea = topRow.removeFromLeft (halfW).reduced (2);
compTitle.setBounds (compArea.removeFromTop (14));
int kw = compArea.getWidth() / 2;
auto c1 = compArea.removeFromLeft (kw);
compThreshLabel.setBounds (c1.removeFromBottom (12));
compThreshSlider.setBounds (c1);
compRatioLabel.setBounds (compArea.removeFromBottom (12));
compRatioSlider.setBounds (compArea);
}
{
auto eqArea = topRow.reduced (2);
eqTitle.setBounds (eqArea.removeFromTop (14));
int kw = eqArea.getWidth() / 3;
auto c1 = eqArea.removeFromLeft (kw);
eqLoLabel.setBounds (c1.removeFromBottom (12));
eqLoSlider.setBounds (c1);
auto c2 = eqArea.removeFromLeft (kw);
eqMidLabel.setBounds (c2.removeFromBottom (12));
eqMidSlider.setBounds (c2);
eqHiLabel.setBounds (eqArea.removeFromBottom (12));
eqHiSlider.setBounds (eqArea);
}
// Bottom row: Distortion | Reverb
{
auto distArea = area.removeFromLeft (halfW).reduced (2);
distTitle.setBounds (distArea.removeFromTop (14));
int kw = distArea.getWidth() / 2;
auto c1 = distArea.removeFromLeft (kw);
distDriveLabel.setBounds (c1.removeFromBottom (12));
distDriveSlider.setBounds (c1);
distMixLabel.setBounds (distArea.removeFromBottom (12));
distMixSlider.setBounds (distArea);
}
{
auto revArea = area.reduced (2);
reverbTitle.setBounds (revArea.removeFromTop (14));
int kw = revArea.getWidth() / 2;
auto c1 = revArea.removeFromLeft (kw);
reverbSizeLabel.setBounds (c1.removeFromBottom (12));
reverbSizeSlider.setBounds (c1);
reverbDecayLabel.setBounds (revArea.removeFromBottom (12));
reverbDecaySlider.setBounds (revArea);
}
}

45
Source/FxPanel.h Normal file
Fájl megtekintése

@@ -0,0 +1,45 @@
#pragma once
#include <JuceHeader.h>
class FxPanel : public juce::Component
{
public:
FxPanel();
void paint (juce::Graphics& g) override;
void resized() override;
// FX parameter getters (for processor to read)
float getCompThreshold() const { return (float) compThreshSlider.getValue(); }
float getCompRatio() const { return (float) compRatioSlider.getValue(); }
float getEqLo() const { return (float) eqLoSlider.getValue(); }
float getEqMid() const { return (float) eqMidSlider.getValue(); }
float getEqHi() const { return (float) eqHiSlider.getValue(); }
float getDistDrive() const { return (float) distDriveSlider.getValue(); }
float getDistMix() const { return (float) distMixSlider.getValue(); }
float getReverbSize() const { return (float) reverbSizeSlider.getValue(); }
float getReverbDecay() const { return (float) reverbDecaySlider.getValue(); }
private:
// Compressor
juce::Slider compThreshSlider, compRatioSlider;
juce::Label compThreshLabel, compRatioLabel, compTitle;
// EQ
juce::Slider eqLoSlider, eqMidSlider, eqHiSlider;
juce::Label eqLoLabel, eqMidLabel, eqHiLabel, eqTitle;
// Distortion
juce::Slider distDriveSlider, distMixSlider;
juce::Label distDriveLabel, distMixLabel, distTitle;
// Reverb
juce::Slider reverbSizeSlider, reverbDecaySlider;
juce::Label reverbSizeLabel, reverbDecayLabel, reverbTitle;
void setupKnob (juce::Slider& s, juce::Label& l, const juce::String& name,
double min, double max, double val, double step = 0.01);
void setupTitle (juce::Label& l, const juce::String& text);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FxPanel)
};

44
Source/LookAndFeel.cpp Normal file
Fájl megtekintése

@@ -0,0 +1,44 @@
#include "LookAndFeel.h"
InstaDrumsLookAndFeel::InstaDrumsLookAndFeel()
{
setColour (juce::ResizableWindow::backgroundColourId, bgDark);
setColour (juce::Label::textColourId, textPrimary);
setColour (juce::TextButton::buttonColourId, bgMedium);
setColour (juce::TextButton::textColourOffId, textPrimary);
}
void InstaDrumsLookAndFeel::drawRotarySlider (juce::Graphics& g, int x, int y, int width, int height,
float sliderPos, float rotaryStartAngle,
float rotaryEndAngle, juce::Slider& slider)
{
auto bounds = juce::Rectangle<int> (x, y, width, height).toFloat().reduced (2.0f);
auto radius = std::min (bounds.getWidth(), bounds.getHeight()) / 2.0f;
auto centreX = bounds.getCentreX();
auto centreY = bounds.getCentreY();
auto angle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle);
// Background arc
juce::Path bgArc;
bgArc.addCentredArc (centreX, centreY, radius - 2, radius - 2,
0.0f, rotaryStartAngle, rotaryEndAngle, true);
g.setColour (bgLight);
g.strokePath (bgArc, juce::PathStrokeType (3.0f, juce::PathStrokeType::curved,
juce::PathStrokeType::rounded));
// Value arc
juce::Path valueArc;
valueArc.addCentredArc (centreX, centreY, radius - 2, radius - 2,
0.0f, rotaryStartAngle, angle, true);
g.setColour (accent);
g.strokePath (valueArc, juce::PathStrokeType (3.0f, juce::PathStrokeType::curved,
juce::PathStrokeType::rounded));
// Pointer
juce::Path pointer;
auto pointerLength = radius * 0.5f;
pointer.addRectangle (-1.5f, -pointerLength, 3.0f, pointerLength);
pointer.applyTransform (juce::AffineTransform::rotation (angle).translated (centreX, centreY));
g.setColour (textPrimary);
g.fillPath (pointer);
}

20
Source/LookAndFeel.h Normal file
Fájl megtekintése

@@ -0,0 +1,20 @@
#pragma once
#include <JuceHeader.h>
class InstaDrumsLookAndFeel : public juce::LookAndFeel_V4
{
public:
// Colour palette
static inline const juce::Colour bgDark { 0xff1a1a2e };
static inline const juce::Colour bgMedium { 0xff16213e };
static inline const juce::Colour bgLight { 0xff0f3460 };
static inline const juce::Colour textPrimary { 0xffe0e0e0 };
static inline const juce::Colour textSecondary { 0xff888899 };
static inline const juce::Colour accent { 0xff00ff88 };
InstaDrumsLookAndFeel();
void drawRotarySlider (juce::Graphics& g, int x, int y, int width, int height,
float sliderPosProportional, float rotaryStartAngle,
float rotaryEndAngle, juce::Slider& slider) override;
};

62
Source/MasterPanel.cpp Normal file
Fájl megtekintése

@@ -0,0 +1,62 @@
#include "MasterPanel.h"
#include "LookAndFeel.h"
MasterPanel::MasterPanel()
{
masterTitle.setFont (juce::FontOptions (12.0f, juce::Font::bold));
masterTitle.setColour (juce::Label::textColourId, InstaDrumsLookAndFeel::textSecondary);
addAndMakeVisible (masterTitle);
setupKnob (volumeSlider, volumeLabel, "Volume", 0.0, 2.0, 1.0, 0.01);
setupKnob (tuneSlider, tuneLabel, "Tune", -12.0, 12.0, 0.0, 0.1);
setupKnob (panSlider, panLabel, "Pan", -1.0, 1.0, 0.0, 0.01);
addAndMakeVisible (vuMeter);
}
void MasterPanel::setupKnob (juce::Slider& s, juce::Label& l, const juce::String& name,
double min, double max, double val, double step)
{
s.setSliderStyle (juce::Slider::RotaryHorizontalVerticalDrag);
s.setTextBoxStyle (juce::Slider::NoTextBox, false, 0, 0);
s.setRange (min, max, step);
s.setValue (val, juce::dontSendNotification);
addAndMakeVisible (s);
l.setText (name, juce::dontSendNotification);
l.setFont (juce::FontOptions (9.0f));
l.setColour (juce::Label::textColourId, InstaDrumsLookAndFeel::textSecondary);
l.setJustificationType (juce::Justification::centred);
addAndMakeVisible (l);
}
void MasterPanel::paint (juce::Graphics& g)
{
auto bounds = getLocalBounds().toFloat();
g.setColour (InstaDrumsLookAndFeel::bgMedium.darker (0.2f));
g.fillRoundedRectangle (bounds, 4.0f);
g.setColour (InstaDrumsLookAndFeel::bgLight.withAlpha (0.3f));
g.drawRoundedRectangle (bounds, 4.0f, 1.0f);
}
void MasterPanel::resized()
{
auto area = getLocalBounds().reduced (4);
masterTitle.setBounds (area.removeFromLeft (55).reduced (0, 2));
// VU meter on the right
vuMeter.setBounds (area.removeFromRight (24).reduced (0, 2));
area.removeFromRight (4);
// Knobs
int knobW = area.getWidth() / 3;
juce::Slider* sliders[] = { &volumeSlider, &tuneSlider, &panSlider };
juce::Label* labels[] = { &volumeLabel, &tuneLabel, &panLabel };
for (int i = 0; i < 3; ++i)
{
auto col = area.removeFromLeft (knobW);
labels[i]->setBounds (col.removeFromBottom (12));
sliders[i]->setBounds (col);
}
}

29
Source/MasterPanel.h Normal file
Fájl megtekintése

@@ -0,0 +1,29 @@
#pragma once
#include <JuceHeader.h>
#include "VuMeter.h"
class MasterPanel : public juce::Component
{
public:
MasterPanel();
void paint (juce::Graphics& g) override;
void resized() override;
float getMasterVolume() const { return (float) volumeSlider.getValue(); }
float getMasterTune() const { return (float) tuneSlider.getValue(); }
float getMasterPan() const { return (float) panSlider.getValue(); }
VuMeter& getVuMeter() { return vuMeter; }
private:
juce::Slider volumeSlider, tuneSlider, panSlider;
juce::Label volumeLabel, tuneLabel, panLabel;
juce::Label masterTitle { {}, "MASTER" };
VuMeter vuMeter;
void setupKnob (juce::Slider& s, juce::Label& l, const juce::String& name,
double min, double max, double val, double step = 0.01);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MasterPanel)
};

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();
}

40
Source/PadComponent.h Normal file
Fájl megtekintése

@@ -0,0 +1,40 @@
#pragma once
#include <JuceHeader.h>
#include "DrumPad.h"
class PadComponent : public juce::Component,
public juce::FileDragAndDropTarget
{
public:
PadComponent (DrumPad& pad, std::function<void(int, const juce::File&)> loadCallback, int padIndex);
void paint (juce::Graphics& g) override;
void resized() override;
void mouseDown (const juce::MouseEvent& event) override;
void mouseUp (const juce::MouseEvent& event) override;
// Drag & Drop
bool isInterestedInFileDrag (const juce::StringArray& files) override;
void filesDropped (const juce::StringArray& files, int x, int y) override;
void fileDragEnter (const juce::StringArray& files, int x, int y) override;
void fileDragExit (const juce::StringArray& files) override;
void setSelected (bool sel) { selected = sel; repaint(); }
bool isSelected() const { return selected; }
// Callback when pad is selected (left click)
std::function<void(int)> onSelected;
private:
DrumPad& drumPad;
std::function<void(int, const juce::File&)> onLoadSample;
int index;
bool isPressed = false;
bool isDragOver = false;
bool selected = false;
void drawWaveformThumbnail (juce::Graphics& g, juce::Rectangle<float> area);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PadComponent)
};

217
Source/PluginEditor.cpp Normal file
Fájl megtekintése

@@ -0,0 +1,217 @@
#include "PluginEditor.h"
InstaDrumsEditor::InstaDrumsEditor (InstaDrumsProcessor& p)
: AudioProcessorEditor (&p), processor (p)
{
setLookAndFeel (&customLookAndFeel);
// Title
titleLabel.setFont (juce::FontOptions (20.0f, juce::Font::bold));
titleLabel.setColour (juce::Label::textColourId, InstaDrumsLookAndFeel::accent);
titleLabel.setJustificationType (juce::Justification::centredLeft);
addAndMakeVisible (titleLabel);
versionLabel.setFont (juce::FontOptions (10.0f));
versionLabel.setColour (juce::Label::textColourId, InstaDrumsLookAndFeel::textSecondary);
versionLabel.setJustificationType (juce::Justification::centredRight);
addAndMakeVisible (versionLabel);
padsLabel.setFont (juce::FontOptions (10.0f, juce::Font::bold));
padsLabel.setColour (juce::Label::textColourId, InstaDrumsLookAndFeel::textSecondary);
addAndMakeVisible (padsLabel);
// Buttons
auto styleBtn = [this] (juce::TextButton& btn) {
btn.setColour (juce::TextButton::buttonColourId, InstaDrumsLookAndFeel::bgLight);
btn.setColour (juce::TextButton::textColourOffId, InstaDrumsLookAndFeel::textPrimary);
addAndMakeVisible (btn);
};
styleBtn (loadSampleButton);
styleBtn (saveKitButton);
styleBtn (loadKitButton);
styleBtn (loadFolderButton);
loadSampleButton.onClick = [this]
{
fileChooser = std::make_unique<juce::FileChooser> ("Load Sample", juce::File{},
"*.wav;*.aiff;*.aif;*.flac;*.ogg;*.mp3");
fileChooser->launchAsync (juce::FileBrowserComponent::openMode | juce::FileBrowserComponent::canSelectFiles,
[this] (const juce::FileChooser& fc) {
auto file = fc.getResult();
if (file.existsAsFile())
{
processor.loadSample (selectedPadIndex, file);
sampleEditor.updateFromPad();
}
});
};
saveKitButton.onClick = [this]
{
fileChooser = std::make_unique<juce::FileChooser> ("Save Kit", juce::File{}, "*.drumkit");
fileChooser->launchAsync (juce::FileBrowserComponent::saveMode,
[this] (const juce::FileChooser& fc) {
auto file = fc.getResult();
if (file != juce::File{})
processor.saveKitPreset (file.hasFileExtension (".drumkit") ? file : file.withFileExtension ("drumkit"));
});
};
loadKitButton.onClick = [this]
{
fileChooser = std::make_unique<juce::FileChooser> ("Load Kit", juce::File{}, "*.drumkit");
fileChooser->launchAsync (juce::FileBrowserComponent::openMode | juce::FileBrowserComponent::canSelectFiles,
[this] (const juce::FileChooser& fc) {
auto file = fc.getResult();
if (file.existsAsFile())
{
processor.loadKitPreset (file);
rebuildPadGrid();
selectPad (0);
}
});
};
loadFolderButton.onClick = [this]
{
fileChooser = std::make_unique<juce::FileChooser> ("Select Sample Folder", juce::File{});
fileChooser->launchAsync (juce::FileBrowserComponent::openMode | juce::FileBrowserComponent::canSelectDirectories,
[this] (const juce::FileChooser& fc) {
auto folder = fc.getResult();
if (folder.isDirectory())
{
processor.loadKitFromFolder (folder);
rebuildPadGrid();
selectPad (0);
}
});
};
// Panels
addAndMakeVisible (sampleEditor);
addAndMakeVisible (fxPanel);
addAndMakeVisible (masterPanel);
rebuildPadGrid();
selectPad (0);
// Sizing
constrainer.setMinimumSize (800, 500);
constrainer.setMaximumSize (1920, 1080);
setConstrainer (&constrainer);
setSize (960, 600);
setResizable (true, true);
startTimerHz (30);
}
InstaDrumsEditor::~InstaDrumsEditor()
{
setLookAndFeel (nullptr);
}
void InstaDrumsEditor::rebuildPadGrid()
{
padComponents.clear();
auto loadCallback = [this] (int padIndex, const juce::File& file) {
processor.loadSample (padIndex, file);
if (padIndex == selectedPadIndex)
sampleEditor.updateFromPad();
};
for (int i = 0; i < processor.getNumPads(); ++i)
{
auto* pc = new PadComponent (processor.getPad (i), loadCallback, i);
pc->onSelected = [this] (int idx) { selectPad (idx); };
addAndMakeVisible (pc);
padComponents.add (pc);
}
resized();
}
void InstaDrumsEditor::selectPad (int index)
{
selectedPadIndex = index;
for (int i = 0; i < padComponents.size(); ++i)
padComponents[i]->setSelected (i == index);
if (index >= 0 && index < processor.getNumPads())
sampleEditor.setPad (&processor.getPad (index));
}
void InstaDrumsEditor::paint (juce::Graphics& g)
{
g.fillAll (InstaDrumsLookAndFeel::bgDark);
// Subtle divider lines
auto bounds = getLocalBounds();
int rightPanelX = (int) (bounds.getWidth() * 0.52f);
int bottomPanelY = bounds.getHeight() - 56;
g.setColour (InstaDrumsLookAndFeel::bgLight.withAlpha (0.3f));
g.drawVerticalLine (rightPanelX - 2, 30, (float) bottomPanelY);
g.drawHorizontalLine (bottomPanelY - 1, 0, (float) bounds.getWidth());
}
void InstaDrumsEditor::resized()
{
auto area = getLocalBounds();
// Top bar (30px)
auto topBar = area.removeFromTop (30).reduced (6, 4);
titleLabel.setBounds (topBar.removeFromLeft (150));
versionLabel.setBounds (topBar.removeFromRight (40));
loadFolderButton.setBounds (topBar.removeFromRight (90).reduced (1));
loadKitButton.setBounds (topBar.removeFromRight (70).reduced (1));
saveKitButton.setBounds (topBar.removeFromRight (70).reduced (1));
loadSampleButton.setBounds (topBar.removeFromRight (95).reduced (1));
// Bottom master bar (52px)
masterPanel.setBounds (area.removeFromBottom (52).reduced (4, 2));
// Left panel: pad grid (~52% width)
int rightPanelX = (int) (area.getWidth() * 0.52f);
auto leftArea = area.removeFromLeft (rightPanelX).reduced (4);
// Pads label
auto padsHeader = leftArea.removeFromTop (16);
padsLabel.setBounds (padsHeader);
// Pad grid
int numPads = padComponents.size();
if (numPads > 0)
{
int rows = (numPads + padColumns - 1) / padColumns;
int padW = leftArea.getWidth() / padColumns;
int padH = leftArea.getHeight() / rows;
for (int i = 0; i < numPads; ++i)
{
int row = i / padColumns;
int col = i % padColumns;
padComponents[i]->setBounds (leftArea.getX() + col * padW,
leftArea.getY() + row * padH,
padW, padH);
}
}
// Right panel: sample editor (top ~55%) + FX (bottom ~45%)
auto rightArea = area.reduced (4);
int editorHeight = (int) (rightArea.getHeight() * 0.55f);
sampleEditor.setBounds (rightArea.removeFromTop (editorHeight).reduced (0, 2));
fxPanel.setBounds (rightArea.reduced (0, 2));
}
void InstaDrumsEditor::timerCallback()
{
for (auto* pc : padComponents)
pc->repaint();
// Update VU meter from processor output levels
// (simplified: just repaint for now)
masterPanel.getVuMeter().repaint();
}

58
Source/PluginEditor.h Normal file
Fájl megtekintése

@@ -0,0 +1,58 @@
#pragma once
#include <JuceHeader.h>
#include "PluginProcessor.h"
#include "PadComponent.h"
#include "SampleEditorPanel.h"
#include "FxPanel.h"
#include "MasterPanel.h"
#include "LookAndFeel.h"
class InstaDrumsEditor : public juce::AudioProcessorEditor,
private juce::Timer
{
public:
explicit InstaDrumsEditor (InstaDrumsProcessor&);
~InstaDrumsEditor() override;
void paint (juce::Graphics&) override;
void resized() override;
private:
InstaDrumsProcessor& processor;
InstaDrumsLookAndFeel customLookAndFeel;
// Pad grid (left side)
juce::OwnedArray<PadComponent> padComponents;
static constexpr int padColumns = 4;
// Right side panels
SampleEditorPanel sampleEditor;
FxPanel fxPanel;
// Bottom
MasterPanel masterPanel;
// Top bar buttons
juce::TextButton loadSampleButton { "LOAD SAMPLE" };
juce::TextButton saveKitButton { "SAVE KIT" };
juce::TextButton loadKitButton { "LOAD KIT" };
juce::TextButton loadFolderButton { "LOAD FOLDER" };
juce::Label titleLabel { {}, "INSTADRUMS" };
juce::Label versionLabel { {}, "v1.0" };
juce::Label padsLabel { {}, "DRUM SAMPLER" };
// State
int selectedPadIndex = 0;
void rebuildPadGrid();
void selectPad (int index);
void timerCallback() override;
std::unique_ptr<juce::FileChooser> fileChooser;
// Resizable
juce::ComponentBoundsConstrainer constrainer;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InstaDrumsEditor)
};

347
Source/PluginProcessor.cpp Normal file
Fájl megtekintése

@@ -0,0 +1,347 @@
#include "PluginProcessor.h"
#include "PluginEditor.h"
InstaDrumsProcessor::InstaDrumsProcessor()
: AudioProcessor (BusesProperties()
.withOutput ("Main", juce::AudioChannelSet::stereo(), true))
{
formatManager.registerBasicFormats();
initializeDefaults();
}
InstaDrumsProcessor::~InstaDrumsProcessor() {}
void InstaDrumsProcessor::initializeDefaults()
{
// GM Drum Map defaults for first 12 pads
struct PadDefault { int note; const char* name; juce::uint32 colour; };
static const PadDefault defaults[] = {
{ 36, "Kick", 0xffff4444 }, // Red
{ 38, "Snare", 0xffff8844 }, // Orange
{ 42, "CH Hat", 0xffffff44 }, // Yellow
{ 46, "OH Hat", 0xff88ff44 }, // Green
{ 45, "Low Tom", 0xff44ffaa }, // Teal
{ 48, "Mid Tom", 0xff44ddff }, // Cyan
{ 50, "Hi Tom", 0xff4488ff }, // Blue
{ 49, "Crash", 0xff8844ff }, // Purple
{ 51, "Ride", 0xffcc44ff }, // Magenta
{ 39, "Clap", 0xffff44cc }, // Pink
{ 56, "Cowbell", 0xffff8888 }, // Light red
{ 37, "Rimshot", 0xffaaaaff }, // Light blue
};
for (int i = 0; i < defaultNumPads && i < (int) std::size (defaults); ++i)
{
pads[i].midiNote = defaults[i].note;
pads[i].name = defaults[i].name;
pads[i].colour = juce::Colour (defaults[i].colour);
}
}
void InstaDrumsProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
{
for (int i = 0; i < numActivePads; ++i)
pads[i].prepareToPlay (sampleRate, samplesPerBlock);
}
void InstaDrumsProcessor::releaseResources()
{
for (int i = 0; i < numActivePads; ++i)
pads[i].releaseResources();
}
void InstaDrumsProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
juce::ScopedNoDenormals noDenormals;
buffer.clear();
// Process MIDI messages
for (const auto metadata : midiMessages)
{
auto msg = metadata.getMessage();
if (msg.isNoteOn())
{
auto* pad = findPadForNote (msg.getNoteNumber());
if (pad != nullptr)
{
// Handle choke groups
if (pad->chokeGroup >= 0)
{
for (int i = 0; i < numActivePads; ++i)
{
if (&pads[i] != pad && pads[i].chokeGroup == pad->chokeGroup)
pads[i].stop();
}
}
pad->trigger (msg.getFloatVelocity());
}
}
else if (msg.isNoteOff())
{
auto* pad = findPadForNote (msg.getNoteNumber());
if (pad != nullptr && ! pad->oneShot)
pad->stop();
}
}
// Render audio from all pads
for (int i = 0; i < numActivePads; ++i)
pads[i].renderNextBlock (buffer, 0, buffer.getNumSamples());
}
DrumPad* InstaDrumsProcessor::findPadForNote (int midiNote)
{
for (int i = 0; i < numActivePads; ++i)
if (pads[i].midiNote == midiNote)
return &pads[i];
return nullptr;
}
void InstaDrumsProcessor::loadSample (int padIndex, const juce::File& file)
{
if (padIndex < 0 || padIndex >= numActivePads)
return;
if (file.isDirectory())
pads[padIndex].loadLayersFromFolder (file, formatManager);
else
pads[padIndex].loadSample (file, formatManager);
}
void InstaDrumsProcessor::addPads (int count)
{
int newCount = std::min (numActivePads + count, maxPads);
for (int i = numActivePads; i < newCount; ++i)
{
pads[i].name = "Pad " + juce::String (i + 1);
pads[i].midiNote = 36 + i; // Sequential mapping
pads[i].colour = juce::Colour::fromHSV ((float) i / 16.0f, 0.7f, 1.0f, 1.0f);
}
numActivePads = newCount;
}
void InstaDrumsProcessor::getStateInformation (juce::MemoryBlock& destData)
{
juce::XmlElement xml ("InstaDrumsState");
xml.setAttribute ("numPads", numActivePads);
for (int i = 0; i < numActivePads; ++i)
{
auto* padXml = xml.createNewChildElement ("Pad");
padXml->setAttribute ("index", i);
padXml->setAttribute ("name", pads[i].name);
padXml->setAttribute ("midiNote", pads[i].midiNote);
padXml->setAttribute ("volume", (double) pads[i].volume);
padXml->setAttribute ("pan", (double) pads[i].pan);
padXml->setAttribute ("pitch", (double) pads[i].pitch);
padXml->setAttribute ("oneShot", pads[i].oneShot);
padXml->setAttribute ("chokeGroup", pads[i].chokeGroup);
padXml->setAttribute ("attack", (double) pads[i].attack);
padXml->setAttribute ("decay", (double) pads[i].decay);
padXml->setAttribute ("sustain", (double) pads[i].sustain);
padXml->setAttribute ("release", (double) pads[i].release);
padXml->setAttribute ("colour", (int) pads[i].colour.getARGB());
auto lf = pads[i].getLoadedFile();
if (lf.existsAsFile() || lf.isDirectory())
padXml->setAttribute ("samplePath", lf.getFullPathName());
}
copyXmlToBinary (xml, destData);
}
void InstaDrumsProcessor::setStateInformation (const void* data, int sizeInBytes)
{
auto xml = getXmlFromBinary (data, sizeInBytes);
if (xml != nullptr && xml->hasTagName ("InstaDrumsState"))
{
numActivePads = xml->getIntAttribute ("numPads", defaultNumPads);
for (auto* padXml : xml->getChildWithTagNameIterator ("Pad"))
{
int index = padXml->getIntAttribute ("index", -1);
if (index < 0 || index >= numActivePads)
continue;
pads[index].name = padXml->getStringAttribute ("name", "Pad");
pads[index].midiNote = padXml->getIntAttribute ("midiNote", 36 + index);
pads[index].volume = (float) padXml->getDoubleAttribute ("volume", 1.0);
pads[index].pan = (float) padXml->getDoubleAttribute ("pan", 0.0);
pads[index].pitch = (float) padXml->getDoubleAttribute ("pitch", 0.0);
pads[index].oneShot = padXml->getBoolAttribute ("oneShot", true);
pads[index].chokeGroup = padXml->getIntAttribute ("chokeGroup", -1);
pads[index].attack = (float) padXml->getDoubleAttribute ("attack", 0.001);
pads[index].decay = (float) padXml->getDoubleAttribute ("decay", 0.1);
pads[index].sustain = (float) padXml->getDoubleAttribute ("sustain", 1.0);
pads[index].release = (float) padXml->getDoubleAttribute ("release", 0.05);
pads[index].colour = juce::Colour ((juce::uint32) padXml->getIntAttribute ("colour", 0xff00ff88));
juce::String path = padXml->getStringAttribute ("samplePath");
if (path.isNotEmpty())
{
juce::File sampleFile (path);
if (sampleFile.isDirectory())
pads[index].loadLayersFromFolder (sampleFile, formatManager);
else if (sampleFile.existsAsFile())
pads[index].loadSample (sampleFile, formatManager);
}
}
}
}
void InstaDrumsProcessor::loadKitFromFolder (const juce::File& folder)
{
if (! folder.isDirectory())
return;
// Collect audio files from the folder
juce::Array<juce::File> audioFiles;
for (auto& f : folder.findChildFiles (juce::File::findFiles, false))
{
auto ext = f.getFileExtension().toLowerCase();
if (ext == ".wav" || ext == ".aiff" || ext == ".aif" || ext == ".flac"
|| ext == ".ogg" || ext == ".mp3")
audioFiles.add (f);
}
audioFiles.sort();
// Try to match files to pads by name (kick, snare, etc.)
auto matchPad = [&] (const juce::String& fileName) -> int
{
auto lower = fileName.toLowerCase();
struct NameMatch { const char* keyword; int padIndex; };
static const NameMatch matches[] = {
{ "kick", 0 }, { "bass", 0 }, { "bd", 0 },
{ "snare", 1 }, { "sn", 1 }, { "sd", 1 },
{ "closedhihat", 2 }, { "closedhi", 2 }, { "chh", 2 }, { "ch hat", 2 },
{ "openhihat", 3 }, { "openhi", 3 }, { "ohh", 3 }, { "oh hat", 3 },
{ "lowtom", 4 }, { "low tom", 4 }, { "lt", 4 },
{ "midtom", 5 }, { "mid tom", 5 }, { "mt", 5 },
{ "hitom", 6 }, { "hi tom", 6 }, { "ht", 6 },
{ "crash", 7 },
{ "ride", 8 },
{ "clap", 9 },
{ "cowbell", 10 }, { "bell", 10 },
{ "rim", 11 },
};
for (auto& m : matches)
if (lower.contains (m.keyword))
return m.padIndex;
return -1;
};
// First pass: match by name
juce::Array<bool> assigned;
assigned.resize (numActivePads);
for (int i = 0; i < numActivePads; ++i)
assigned.set (i, false);
for (auto& file : audioFiles)
{
int idx = matchPad (file.getFileNameWithoutExtension());
if (idx >= 0 && idx < numActivePads && ! assigned[idx])
{
pads[idx].loadSample (file, formatManager);
assigned.set (idx, true);
}
}
// Second pass: assign remaining files to unassigned pads
int nextPad = 0;
for (auto& file : audioFiles)
{
int idx = matchPad (file.getFileNameWithoutExtension());
if (idx >= 0 && idx < numActivePads && assigned[idx])
continue; // Already assigned
while (nextPad < numActivePads && assigned[nextPad])
nextPad++;
if (nextPad < numActivePads)
{
pads[nextPad].loadSample (file, formatManager);
assigned.set (nextPad, true);
nextPad++;
}
}
}
void InstaDrumsProcessor::saveKitPreset (const juce::File& file)
{
juce::XmlElement xml ("InstaDrumsKit");
xml.setAttribute ("version", "1.0");
xml.setAttribute ("numPads", numActivePads);
for (int i = 0; i < numActivePads; ++i)
{
auto* padXml = xml.createNewChildElement ("Pad");
padXml->setAttribute ("index", i);
padXml->setAttribute ("name", pads[i].name);
padXml->setAttribute ("midiNote", pads[i].midiNote);
padXml->setAttribute ("volume", (double) pads[i].volume);
padXml->setAttribute ("pan", (double) pads[i].pan);
padXml->setAttribute ("pitch", (double) pads[i].pitch);
padXml->setAttribute ("oneShot", pads[i].oneShot);
padXml->setAttribute ("chokeGroup", pads[i].chokeGroup);
padXml->setAttribute ("attack", (double) pads[i].attack);
padXml->setAttribute ("decay", (double) pads[i].decay);
padXml->setAttribute ("sustain", (double) pads[i].sustain);
padXml->setAttribute ("release", (double) pads[i].release);
padXml->setAttribute ("colour", (int) pads[i].colour.getARGB());
auto lf = pads[i].getLoadedFile();
if (lf.existsAsFile() || lf.isDirectory())
padXml->setAttribute ("samplePath", lf.getFullPathName());
}
xml.writeTo (file);
}
void InstaDrumsProcessor::loadKitPreset (const juce::File& file)
{
auto xml = juce::XmlDocument::parse (file);
if (xml == nullptr || ! xml->hasTagName ("InstaDrumsKit"))
return;
numActivePads = xml->getIntAttribute ("numPads", defaultNumPads);
for (auto* padXml : xml->getChildWithTagNameIterator ("Pad"))
{
int index = padXml->getIntAttribute ("index", -1);
if (index < 0 || index >= numActivePads)
continue;
pads[index].name = padXml->getStringAttribute ("name", pads[index].name);
pads[index].midiNote = padXml->getIntAttribute ("midiNote", pads[index].midiNote);
pads[index].volume = (float) padXml->getDoubleAttribute ("volume", 1.0);
pads[index].pan = (float) padXml->getDoubleAttribute ("pan", 0.0);
pads[index].pitch = (float) padXml->getDoubleAttribute ("pitch", 0.0);
pads[index].oneShot = padXml->getBoolAttribute ("oneShot", true);
pads[index].chokeGroup = padXml->getIntAttribute ("chokeGroup", -1);
pads[index].attack = (float) padXml->getDoubleAttribute ("attack", 0.001);
pads[index].decay = (float) padXml->getDoubleAttribute ("decay", 0.1);
pads[index].sustain = (float) padXml->getDoubleAttribute ("sustain", 1.0);
pads[index].release = (float) padXml->getDoubleAttribute ("release", 0.05);
pads[index].colour = juce::Colour ((juce::uint32) padXml->getIntAttribute ("colour", (int) pads[index].colour.getARGB()));
juce::String path = padXml->getStringAttribute ("samplePath");
if (path.isNotEmpty())
{
juce::File sampleFile (path);
if (sampleFile.existsAsFile())
pads[index].loadSample (sampleFile, formatManager);
}
}
}
juce::AudioProcessorEditor* InstaDrumsProcessor::createEditor()
{
return new InstaDrumsEditor (*this);
}
juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter()
{
return new InstaDrumsProcessor();
}

60
Source/PluginProcessor.h Normal file
Fájl megtekintése

@@ -0,0 +1,60 @@
#pragma once
#include <JuceHeader.h>
#include "DrumPad.h"
class InstaDrumsProcessor : public juce::AudioProcessor
{
public:
static constexpr int defaultNumPads = 12;
static constexpr int maxPads = 64;
InstaDrumsProcessor();
~InstaDrumsProcessor() override;
void prepareToPlay (double sampleRate, int samplesPerBlock) override;
void releaseResources() override;
void processBlock (juce::AudioBuffer<float>&, juce::MidiBuffer&) override;
juce::AudioProcessorEditor* createEditor() override;
bool hasEditor() const override { return true; }
const juce::String getName() const override { return JucePlugin_Name; }
bool acceptsMidi() const override { return true; }
bool producesMidi() const override { return true; }
double getTailLengthSeconds() const override { return 0.0; }
int getNumPrograms() override { return 1; }
int getCurrentProgram() override { return 0; }
void setCurrentProgram (int) override {}
const juce::String getProgramName (int) override { return {}; }
void changeProgramName (int, const juce::String&) override {}
void getStateInformation (juce::MemoryBlock& destData) override;
void setStateInformation (const void* data, int sizeInBytes) override;
// Pad management
int getNumPads() const { return numActivePads; }
DrumPad& getPad (int index) { return pads[index]; }
void addPads (int count = 4);
void loadSample (int padIndex, const juce::File& file);
juce::AudioFormatManager& getFormatManager() { return formatManager; }
// Find pad by MIDI note
DrumPad* findPadForNote (int midiNote);
// Kit management
void loadKitFromFolder (const juce::File& folder);
void saveKitPreset (const juce::File& file);
void loadKitPreset (const juce::File& file);
private:
std::array<DrumPad, maxPads> pads;
int numActivePads = defaultNumPads;
juce::AudioFormatManager formatManager;
// Default MIDI mapping (GM drum map)
void initializeDefaults();
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InstaDrumsProcessor)
};

Fájl megtekintése

@@ -0,0 +1,144 @@
#include "SampleEditorPanel.h"
#include "LookAndFeel.h"
SampleEditorPanel::SampleEditorPanel()
{
titleLabel.setFont (juce::FontOptions (14.0f, juce::Font::bold));
titleLabel.setColour (juce::Label::textColourId, InstaDrumsLookAndFeel::textSecondary);
addAndMakeVisible (titleLabel);
padNameLabel.setFont (juce::FontOptions (13.0f, juce::Font::bold));
padNameLabel.setColour (juce::Label::textColourId, InstaDrumsLookAndFeel::accent);
addAndMakeVisible (padNameLabel);
waveform.setShowADSR (true);
addAndMakeVisible (waveform);
setupKnob (attackSlider, attackLabel, "Attack", 0.0, 1.0, 0.001, 0.001);
setupKnob (decaySlider, decayLabel, "Decay", 0.0, 2.0, 0.1, 0.01);
setupKnob (sustainSlider, sustainLabel, "Sustain", 0.0, 1.0, 1.0, 0.01);
setupKnob (releaseSlider, releaseLabel, "Release", 0.0, 2.0, 0.05, 0.01);
setupKnob (pitchSlider, pitchLabel, "Pitch", -24.0, 24.0, 0.0, 0.1);
setupKnob (panSlider, panLabel, "Pan", -1.0, 1.0, 0.0, 0.01);
setupKnob (cutoffSlider, cutoffLabel, "Cutoff", 20.0, 20000.0, 20000.0, 1.0);
setupKnob (resoSlider, resoLabel, "Reso", 0.1, 10.0, 0.707, 0.01);
cutoffSlider.setSkewFactorFromMidPoint (1000.0);
}
void SampleEditorPanel::setupKnob (juce::Slider& s, juce::Label& l, const juce::String& name,
double min, double max, double val, double step)
{
s.setSliderStyle (juce::Slider::RotaryHorizontalVerticalDrag);
s.setTextBoxStyle (juce::Slider::NoTextBox, false, 0, 0);
s.setRange (min, max, step);
s.setValue (val, juce::dontSendNotification);
s.addListener (this);
addAndMakeVisible (s);
l.setText (name, juce::dontSendNotification);
l.setFont (juce::FontOptions (9.0f));
l.setColour (juce::Label::textColourId, InstaDrumsLookAndFeel::textSecondary);
l.setJustificationType (juce::Justification::centred);
addAndMakeVisible (l);
}
void SampleEditorPanel::setPad (DrumPad* pad)
{
currentPad = pad;
updateFromPad();
}
void SampleEditorPanel::updateFromPad()
{
if (currentPad == nullptr) return;
padNameLabel.setText (currentPad->name, juce::dontSendNotification);
attackSlider.setValue (currentPad->attack, juce::dontSendNotification);
decaySlider.setValue (currentPad->decay, juce::dontSendNotification);
sustainSlider.setValue (currentPad->sustain, juce::dontSendNotification);
releaseSlider.setValue (currentPad->release, juce::dontSendNotification);
pitchSlider.setValue (currentPad->pitch, juce::dontSendNotification);
panSlider.setValue (currentPad->pan, juce::dontSendNotification);
auto& buf = currentPad->getSampleBuffer();
waveform.setBuffer (&buf);
waveform.setColour (currentPad->colour);
waveform.setADSR (currentPad->attack, currentPad->decay, currentPad->sustain, currentPad->release);
repaint();
}
void SampleEditorPanel::sliderValueChanged (juce::Slider* slider)
{
if (currentPad == nullptr) return;
if (slider == &attackSlider) currentPad->attack = (float) slider->getValue();
else if (slider == &decaySlider) currentPad->decay = (float) slider->getValue();
else if (slider == &sustainSlider) currentPad->sustain = (float) slider->getValue();
else if (slider == &releaseSlider) currentPad->release = (float) slider->getValue();
else if (slider == &pitchSlider) currentPad->pitch = (float) slider->getValue();
else if (slider == &panSlider) currentPad->pan = (float) slider->getValue();
// Update ADSR overlay
waveform.setADSR (currentPad->attack, currentPad->decay, currentPad->sustain, currentPad->release);
}
void SampleEditorPanel::paint (juce::Graphics& g)
{
auto bounds = getLocalBounds().toFloat();
g.setColour (InstaDrumsLookAndFeel::bgMedium);
g.fillRoundedRectangle (bounds, 6.0f);
g.setColour (InstaDrumsLookAndFeel::bgLight.withAlpha (0.5f));
g.drawRoundedRectangle (bounds, 6.0f, 1.0f);
}
void SampleEditorPanel::resized()
{
auto area = getLocalBounds().reduced (6);
// Header
auto header = area.removeFromTop (20);
titleLabel.setBounds (header.removeFromLeft (100));
padNameLabel.setBounds (header);
area.removeFromTop (2);
// Waveform (top portion ~40%)
int waveHeight = std::max (60, (int) (area.getHeight() * 0.38f));
waveform.setBounds (area.removeFromTop (waveHeight));
area.removeFromTop (4);
// ADSR knobs row
int knobH = std::max (40, (int) (area.getHeight() * 0.45f));
auto adsrRow = area.removeFromTop (knobH);
int knobW = adsrRow.getWidth() / 4;
{
juce::Slider* s[] = { &attackSlider, &decaySlider, &sustainSlider, &releaseSlider };
juce::Label* l[] = { &attackLabel, &decayLabel, &sustainLabel, &releaseLabel };
for (int i = 0; i < 4; ++i)
{
auto col = adsrRow.removeFromLeft (knobW);
l[i]->setBounds (col.removeFromBottom (14));
s[i]->setBounds (col);
}
}
area.removeFromTop (2);
// Bottom row: Pitch, Pan, Cutoff, Reso
auto bottomRow = area;
knobW = bottomRow.getWidth() / 4;
{
juce::Slider* s[] = { &pitchSlider, &panSlider, &cutoffSlider, &resoSlider };
juce::Label* l[] = { &pitchLabel, &panLabel, &cutoffLabel, &resoLabel };
for (int i = 0; i < 4; ++i)
{
auto col = bottomRow.removeFromLeft (knobW);
l[i]->setBounds (col.removeFromBottom (14));
s[i]->setBounds (col);
}
}
}

Fájl megtekintése

@@ -0,0 +1,41 @@
#pragma once
#include <JuceHeader.h>
#include "DrumPad.h"
#include "WaveformDisplay.h"
class SampleEditorPanel : public juce::Component,
public juce::Slider::Listener
{
public:
SampleEditorPanel();
void setPad (DrumPad* pad);
DrumPad* getCurrentPad() const { return currentPad; }
void paint (juce::Graphics& g) override;
void resized() override;
void sliderValueChanged (juce::Slider* slider) override;
void updateFromPad();
private:
DrumPad* currentPad = nullptr;
WaveformDisplay waveform;
// ADSR knobs
juce::Slider attackSlider, decaySlider, sustainSlider, releaseSlider;
juce::Label attackLabel, decayLabel, sustainLabel, releaseLabel;
// Sample controls
juce::Slider pitchSlider, panSlider, cutoffSlider, resoSlider;
juce::Label pitchLabel, panLabel, cutoffLabel, resoLabel;
juce::Label titleLabel { {}, "Sample Editor" };
juce::Label padNameLabel { {}, "" };
void setupKnob (juce::Slider& s, juce::Label& l, const juce::String& name,
double min, double max, double val, double step = 0.01);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SampleEditorPanel)
};

47
Source/VuMeter.h Normal file
Fájl megtekintése

@@ -0,0 +1,47 @@
#pragma once
#include <JuceHeader.h>
class VuMeter : public juce::Component
{
public:
void setLevel (float left, float right)
{
levelL = left;
levelR = right;
repaint();
}
void paint (juce::Graphics& g) override
{
auto bounds = getLocalBounds().toFloat().reduced (1);
float halfW = bounds.getWidth() / 2.0f - 1;
auto leftBar = bounds.removeFromLeft (halfW);
bounds.removeFromLeft (2);
auto rightBar = bounds;
drawBar (g, leftBar, levelL);
drawBar (g, rightBar, levelR);
}
private:
float levelL = 0.0f, levelR = 0.0f;
void drawBar (juce::Graphics& g, juce::Rectangle<float> bar, float level)
{
g.setColour (juce::Colour (0xff222233));
g.fillRoundedRectangle (bar, 2.0f);
float h = bar.getHeight() * juce::jlimit (0.0f, 1.0f, level);
auto filled = bar.removeFromBottom (h);
// Green -> Yellow -> Red gradient
if (level < 0.6f)
g.setColour (juce::Colour (0xff00cc44));
else if (level < 0.85f)
g.setColour (juce::Colour (0xffcccc00));
else
g.setColour (juce::Colour (0xffff3333));
g.fillRoundedRectangle (filled, 2.0f);
}
};

106
Source/WaveformDisplay.cpp Normal file
Fájl megtekintése

@@ -0,0 +1,106 @@
#include "WaveformDisplay.h"
#include "LookAndFeel.h"
WaveformDisplay::WaveformDisplay() {}
void WaveformDisplay::setBuffer (const juce::AudioBuffer<float>* buffer, double sampleRate)
{
audioBuffer = buffer;
bufferSampleRate = sampleRate;
repaint();
}
void WaveformDisplay::paint (juce::Graphics& g)
{
auto bounds = getLocalBounds().toFloat();
// Background
g.setColour (InstaDrumsLookAndFeel::bgDark.darker (0.3f));
g.fillRoundedRectangle (bounds, 4.0f);
if (audioBuffer == nullptr || audioBuffer->getNumSamples() == 0)
return;
const int numSamples = audioBuffer->getNumSamples();
const int startSample = (int) (startPos * numSamples);
const int endSample = (int) (endPos * numSamples);
const int visibleSamples = std::max (1, endSample - startSample);
const float width = bounds.getWidth();
const float height = bounds.getHeight();
const float midY = bounds.getCentreY();
// Draw waveform
juce::Path wavePath;
const float* data = audioBuffer->getReadPointer (0);
for (int x = 0; x < (int) width; ++x)
{
int sampleIndex = startSample + (int) ((float) x / width * visibleSamples);
sampleIndex = juce::jlimit (0, numSamples - 1, sampleIndex);
// Find min/max in a small range for better visualization
int blockSize = std::max (1, visibleSamples / (int) width);
float minVal = 1.0f, maxVal = -1.0f;
for (int j = 0; j < blockSize && (sampleIndex + j) < numSamples; ++j)
{
float v = data[sampleIndex + j];
minVal = std::min (minVal, v);
maxVal = std::max (maxVal, v);
}
float topY = midY - maxVal * (height * 0.45f);
float botY = midY - minVal * (height * 0.45f);
if (x == 0)
wavePath.startNewSubPath ((float) x + bounds.getX(), topY);
wavePath.lineTo ((float) x + bounds.getX(), topY);
if (x == (int) width - 1)
{
// Close the path by going back along bottom
for (int bx = (int) width - 1; bx >= 0; --bx)
{
int si = startSample + (int) ((float) bx / width * visibleSamples);
si = juce::jlimit (0, numSamples - 1, si);
float mn = 1.0f;
for (int j = 0; j < blockSize && (si + j) < numSamples; ++j)
mn = std::min (mn, data[si + j]);
float by = midY - mn * (height * 0.45f);
wavePath.lineTo ((float) bx + bounds.getX(), by);
}
wavePath.closeSubPath();
}
}
// Fill waveform
g.setColour (waveColour.withAlpha (0.5f));
g.fillPath (wavePath);
g.setColour (waveColour.withAlpha (0.9f));
g.strokePath (wavePath, juce::PathStrokeType (1.0f));
// Draw ADSR overlay
if (showADSR)
{
float totalSeconds = (float) numSamples / (float) bufferSampleRate;
float ax = adsrA / totalSeconds;
float dx = adsrD / totalSeconds;
float sx = 0.4f; // sustain portion
float rx = adsrR / totalSeconds;
float total = ax + dx + sx + rx;
// Normalize to width
juce::Path adsrPath;
float x0 = bounds.getX();
float w = bounds.getWidth();
adsrPath.startNewSubPath (x0, bounds.getBottom());
adsrPath.lineTo (x0 + (ax / total) * w, bounds.getY() + 4); // attack peak
adsrPath.lineTo (x0 + ((ax + dx) / total) * w, midY - (adsrS - 0.5f) * height * 0.8f); // decay to sustain
adsrPath.lineTo (x0 + ((ax + dx + sx) / total) * w, midY - (adsrS - 0.5f) * height * 0.8f); // sustain hold
adsrPath.lineTo (x0 + w, bounds.getBottom()); // release to 0
g.setColour (InstaDrumsLookAndFeel::accent.withAlpha (0.7f));
g.strokePath (adsrPath, juce::PathStrokeType (2.0f));
}
}

26
Source/WaveformDisplay.h Normal file
Fájl megtekintése

@@ -0,0 +1,26 @@
#pragma once
#include <JuceHeader.h>
class WaveformDisplay : public juce::Component
{
public:
WaveformDisplay();
void setBuffer (const juce::AudioBuffer<float>* buffer, double sampleRate = 44100.0);
void setColour (juce::Colour c) { waveColour = c; repaint(); }
void setStartEnd (float start, float end) { startPos = start; endPos = end; repaint(); }
void setADSR (float a, float d, float s, float r) { adsrA = a; adsrD = d; adsrS = s; adsrR = r; repaint(); }
void setShowADSR (bool show) { showADSR = show; repaint(); }
void paint (juce::Graphics& g) override;
private:
const juce::AudioBuffer<float>* audioBuffer = nullptr;
double bufferSampleRate = 44100.0;
juce::Colour waveColour { 0xffff8844 };
float startPos = 0.0f, endPos = 1.0f;
float adsrA = 0.001f, adsrD = 0.1f, adsrS = 1.0f, adsrR = 0.05f;
bool showADSR = false;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WaveformDisplay)
};