2026-07-13 20:57:38 +02:00
|
|
|
#pragma once
|
|
|
|
|
#include <juce_audio_processors/juce_audio_processors.h>
|
|
|
|
|
#include <juce_audio_basics/juce_audio_basics.h>
|
|
|
|
|
#include <juce_dsp/juce_dsp.h>
|
|
|
|
|
#include "DSP/Voice.h"
|
|
|
|
|
#include "DSP/Distortion.h"
|
|
|
|
|
#include "DSP/Compressor.h"
|
|
|
|
|
#include "DSP/Delay.h"
|
|
|
|
|
#include "DSP/Reverb.h"
|
|
|
|
|
#include "PresetManager.h"
|
|
|
|
|
#include <array>
|
|
|
|
|
#include <atomic>
|
|
|
|
|
|
2026-07-14 01:19:44 +02:00
|
|
|
const juce::StringArray& getSemitoneChoices();
|
|
|
|
|
|
2026-07-13 20:57:38 +02:00
|
|
|
class ChromaFlockProcessor : public juce::AudioProcessor {
|
|
|
|
|
public:
|
|
|
|
|
ChromaFlockProcessor();
|
|
|
|
|
~ChromaFlockProcessor() 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 "ChromaFlock"; }
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
juce::AudioProcessorValueTreeState apvts;
|
|
|
|
|
PresetManager presetManager;
|
|
|
|
|
|
|
|
|
|
static constexpr int scopeBufferSize = 1024;
|
|
|
|
|
std::array<float, scopeBufferSize> scopeBuffer{};
|
|
|
|
|
std::atomic<int> scopeWritePos{0};
|
|
|
|
|
|
|
|
|
|
static constexpr int fftOrder = 11;
|
|
|
|
|
static constexpr int fftSize = 1 << fftOrder;
|
|
|
|
|
std::array<float, fftSize> fftInput{};
|
|
|
|
|
std::atomic<int> fftWritePos{0};
|
|
|
|
|
|
|
|
|
|
float getRmsLevel() const { return rmsLevel.load(); }
|
|
|
|
|
|
|
|
|
|
void noteOn(int midiNote, float velocity) {
|
|
|
|
|
if (midiNote >= 0 && midiNote < 128)
|
|
|
|
|
activeNotes[midiNote].store(true, std::memory_order_relaxed);
|
2026-07-14 01:19:44 +02:00
|
|
|
synth.noteOn(1, juce::jlimit(0, 127, midiNote + getTransposeSemitones()), velocity);
|
2026-07-13 20:57:38 +02:00
|
|
|
}
|
|
|
|
|
void noteOff(int midiNote) {
|
|
|
|
|
if (midiNote >= 0 && midiNote < 128)
|
|
|
|
|
activeNotes[midiNote].store(false, std::memory_order_relaxed);
|
2026-07-14 01:19:44 +02:00
|
|
|
synth.noteOff(1, juce::jlimit(0, 127, midiNote + getTransposeSemitones()), 0.0f, true);
|
2026-07-13 20:57:38 +02:00
|
|
|
}
|
|
|
|
|
bool isNoteActive(int midiNote) const {
|
|
|
|
|
return midiNote >= 0 && midiNote < 128
|
|
|
|
|
&& activeNotes[midiNote].load(std::memory_order_relaxed);
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-14 01:19:44 +02:00
|
|
|
int getTransposeSemitones() const;
|
|
|
|
|
|
2026-07-14 13:54:17 +02:00
|
|
|
// Fade out all currently sounding voices over `seconds` so a long release
|
|
|
|
|
// tail from the previous preset is silenced on preset change.
|
|
|
|
|
void fadeOutActiveVoices(float seconds);
|
|
|
|
|
|
2026-07-13 20:57:38 +02:00
|
|
|
std::atomic<float> pitchBendValue{0.0f};
|
|
|
|
|
float getPitchBend() const { return pitchBendValue.load(std::memory_order_relaxed); }
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout();
|
|
|
|
|
|
|
|
|
|
void updateVoiceParameters();
|
|
|
|
|
|
|
|
|
|
juce::Synthesiser synth;
|
|
|
|
|
static constexpr int maxVoices = 16;
|
|
|
|
|
|
|
|
|
|
Distortion distortion;
|
|
|
|
|
Compressor compressor;
|
|
|
|
|
Delay delay;
|
|
|
|
|
ReverbFX reverbFX;
|
|
|
|
|
float autoPanPhase = 0.0f;
|
|
|
|
|
|
|
|
|
|
double currentSampleRate = 44100.0;
|
2026-07-14 01:18:46 +02:00
|
|
|
double currentBpm = 120.0;
|
2026-07-13 20:57:38 +02:00
|
|
|
|
|
|
|
|
std::atomic<float> rmsLevel{0.0f};
|
|
|
|
|
std::array<std::atomic<bool>, 128> activeNotes{};
|
|
|
|
|
|
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ChromaFlockProcessor)
|
|
|
|
|
};
|