2026-07-19 11:40:36 +02:00
|
|
|
#pragma once
|
|
|
|
|
#include <juce_audio_processors/juce_audio_processors.h>
|
|
|
|
|
#include <juce_audio_formats/juce_audio_formats.h>
|
2026-07-19 18:53:17 +02:00
|
|
|
#include <juce_dsp/juce_dsp.h>
|
2026-07-20 12:33:36 +02:00
|
|
|
#include <atomic>
|
2026-07-19 18:53:17 +02:00
|
|
|
#include <random>
|
2026-07-19 11:40:36 +02:00
|
|
|
|
|
|
|
|
static constexpr int NUM_TRACKS = 10;
|
|
|
|
|
static constexpr int MAX_VOICES = 32;
|
|
|
|
|
static constexpr int BASE_NOTE = 60;
|
|
|
|
|
|
|
|
|
|
class TrommelkisteProcessor : public juce::AudioProcessor
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
TrommelkisteProcessor();
|
|
|
|
|
~TrommelkisteProcessor() 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 "Trommelkiste"; }
|
|
|
|
|
bool acceptsMidi() const override { return true; }
|
|
|
|
|
bool producesMidi() const override { return false; }
|
|
|
|
|
bool isMidiEffect() const override { return false; }
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
std::optional<juce::String> getNameForMidiNoteNumber(int note, int) override;
|
|
|
|
|
|
|
|
|
|
void loadSample(int trackIndex, const juce::File& file);
|
|
|
|
|
void clearSample(int trackIndex);
|
|
|
|
|
|
|
|
|
|
struct Track
|
|
|
|
|
{
|
|
|
|
|
juce::AudioBuffer<float> buffer;
|
|
|
|
|
int fileSampleRate = 44100;
|
|
|
|
|
juce::SpinLock lock;
|
|
|
|
|
juce::String filePath;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Track tracks[NUM_TRACKS];
|
2026-07-20 12:33:36 +02:00
|
|
|
std::atomic<bool> trackActive[NUM_TRACKS] = {};
|
2026-07-20 13:26:16 +02:00
|
|
|
std::atomic<float> outputLevel { 0.0f };
|
2026-07-19 11:40:36 +02:00
|
|
|
juce::AudioProcessorValueTreeState apvts;
|
|
|
|
|
|
|
|
|
|
// Sample library
|
|
|
|
|
struct SampleRef
|
|
|
|
|
{
|
|
|
|
|
juce::String name;
|
|
|
|
|
juce::File file;
|
|
|
|
|
const char* data = nullptr;
|
|
|
|
|
int dataSize = 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
juce::Array<SampleRef> trackSamples[NUM_TRACKS];
|
|
|
|
|
int currentSampleIndex[NUM_TRACKS] = {};
|
|
|
|
|
void scanSamples();
|
|
|
|
|
void loadSampleByIndex(int trackIndex, int sampleIndex);
|
|
|
|
|
void nextSample(int trackIndex);
|
|
|
|
|
void prevSample(int trackIndex);
|
|
|
|
|
|
|
|
|
|
// Kit / Preset system
|
|
|
|
|
struct Kit
|
|
|
|
|
{
|
|
|
|
|
juce::String name;
|
|
|
|
|
const char* samples[NUM_TRACKS];
|
|
|
|
|
};
|
|
|
|
|
|
2026-08-05 00:21:50 +02:00
|
|
|
int currentPresetIndex = 0;
|
2026-07-19 11:40:36 +02:00
|
|
|
void loadKit(int index);
|
|
|
|
|
int getNumKits() const;
|
|
|
|
|
static const Kit* getKitList();
|
|
|
|
|
|
2026-08-05 00:21:50 +02:00
|
|
|
// User presets (saved to disk, shown in the preset menu)
|
|
|
|
|
struct UserPreset
|
|
|
|
|
{
|
|
|
|
|
juce::String name;
|
|
|
|
|
juce::File file;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
juce::Array<UserPreset> userPresets;
|
|
|
|
|
void saveUserPreset(const juce::String& name);
|
|
|
|
|
void loadUserPreset(int index);
|
|
|
|
|
void refreshUserPresets();
|
|
|
|
|
|
|
|
|
|
int getNumPresets() const { return getNumKits() + userPresets.size(); }
|
|
|
|
|
juce::String getPresetName(int index) const;
|
|
|
|
|
void loadPreset(int index);
|
|
|
|
|
void nextPreset();
|
|
|
|
|
void prevPreset();
|
|
|
|
|
|
2026-07-19 11:40:36 +02:00
|
|
|
private:
|
|
|
|
|
struct Voice
|
|
|
|
|
{
|
|
|
|
|
bool active = false;
|
|
|
|
|
int trackIndex = -1;
|
|
|
|
|
float position = 0.0f;
|
|
|
|
|
int samplesPlayed = 0;
|
|
|
|
|
float filterStateL = 0.0f;
|
|
|
|
|
float filterStateR = 0.0f;
|
|
|
|
|
float noteVelocity = 1.0f;
|
2026-07-19 18:53:17 +02:00
|
|
|
float ringModPhase = 0.0f;
|
2026-07-19 11:40:36 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Voice voices[MAX_VOICES];
|
|
|
|
|
juce::AudioFormatManager formatManager;
|
|
|
|
|
double currentSampleRate = 44100.0;
|
2026-07-19 18:53:17 +02:00
|
|
|
std::mt19937 rng{std::random_device{}()};
|
|
|
|
|
|
|
|
|
|
// Effects
|
|
|
|
|
static constexpr int DELAY_BUFFER_SECONDS = 4;
|
|
|
|
|
juce::AudioBuffer<float> delayBuffer;
|
|
|
|
|
int delayWritePos = 0;
|
|
|
|
|
juce::dsp::Reverb reverb;
|
|
|
|
|
juce::dsp::Reverb::Parameters reverbParams;
|
2026-07-19 11:40:36 +02:00
|
|
|
|
|
|
|
|
int findFreeVoice(int trackIndex);
|
|
|
|
|
void handleNoteOn(int trackIndex, float velocity);
|
|
|
|
|
void handleNoteOff(int trackIndex);
|
|
|
|
|
juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout();
|
|
|
|
|
|
2026-07-20 16:09:31 +02:00
|
|
|
struct PendingShuffleEvent
|
|
|
|
|
{
|
|
|
|
|
int trackIndex = -1;
|
|
|
|
|
float velocity = 0.0f;
|
|
|
|
|
int delaySamples = 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static constexpr int MAX_PENDING_SHUFFLE = 32;
|
|
|
|
|
PendingShuffleEvent pendingShuffle[MAX_PENDING_SHUFFLE];
|
|
|
|
|
int numPendingShuffle = 0;
|
2026-07-20 19:27:07 +02:00
|
|
|
int lastTriggeredTrack = -1;
|
2026-07-20 16:09:31 +02:00
|
|
|
|
2026-07-19 11:40:36 +02:00
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TrommelkisteProcessor)
|
|
|
|
|
};
|