mirror of
https://codeberg.org/armin/trommelkiste.git
synced 2026-09-01 04:10:46 +02:00
Use a latch pattern for trackActive: the audio thread sets it true whenever a voice is active, and the UI timer atomically reads and clears it via exchange(false). This eliminates the race where the flag was cleared at the start of every processBlock, causing the 30Hz timer to miss brief activations from short samples.
119 lines
3.6 KiB
C++
119 lines
3.6 KiB
C++
#pragma once
|
|
#include <juce_audio_processors/juce_audio_processors.h>
|
|
#include <juce_audio_formats/juce_audio_formats.h>
|
|
#include <juce_dsp/juce_dsp.h>
|
|
#include <atomic>
|
|
#include <random>
|
|
|
|
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];
|
|
std::atomic<bool> trackActive[NUM_TRACKS] = {};
|
|
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];
|
|
};
|
|
|
|
int currentKitIndex = 0;
|
|
void loadKit(int index);
|
|
void nextKit();
|
|
void prevKit();
|
|
int getNumKits() const;
|
|
juce::String getCurrentKitName() const;
|
|
static const Kit* getKitList();
|
|
|
|
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<float> 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();
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TrommelkisteProcessor)
|
|
};
|