#pragma once #include #include #include #include #include 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&, 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 getNameForMidiNoteNumber(int note, int) override; void loadSample(int trackIndex, const juce::File& file); void clearSample(int trackIndex); struct Track { juce::AudioBuffer buffer; int fileSampleRate = 44100; juce::SpinLock lock; juce::String filePath; }; Track tracks[NUM_TRACKS]; std::atomic trackActive[NUM_TRACKS] = {}; std::atomic outputLevel { 0.0f }; juce::AudioProcessorValueTreeState apvts; // Sample library struct SampleRef { juce::String name; juce::File file; const char* data = nullptr; int dataSize = 0; }; juce::Array 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]; }; int currentPresetIndex = 0; void loadKit(int index); int getNumKits() const; static const Kit* getKitList(); // User presets (saved to disk, shown in the preset menu) struct UserPreset { juce::String name; juce::File file; }; juce::Array 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(); 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; float ringModPhase = 0.0f; }; Voice voices[MAX_VOICES]; juce::AudioFormatManager formatManager; double currentSampleRate = 44100.0; std::mt19937 rng{std::random_device{}()}; // Effects static constexpr int DELAY_BUFFER_SECONDS = 4; juce::AudioBuffer delayBuffer; int delayWritePos = 0; juce::dsp::Reverb reverb; juce::dsp::Reverb::Parameters reverbParams; int findFreeVoice(int trackIndex); void handleNoteOn(int trackIndex, float velocity); void handleNoteOff(int trackIndex); juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); 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; int lastTriggeredTrack = -1; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TrommelkisteProcessor) };