mindball/Source/PluginProcessor.cpp
2026-08-14 21:14:18 +02:00

196 lines
7.9 KiB
C++

#include "PluginProcessor.h"
#include "PluginEditor.h"
MindballAudioProcessor::MindballAudioProcessor()
: AudioProcessor (BusesProperties()
.withInput ("Input", juce::AudioChannelSet::stereo(), true)
.withOutput ("Output", juce::AudioChannelSet::stereo(), true)),
apvts (*this, nullptr, "Parameters", createParameterLayout())
{
}
juce::AudioProcessorValueTreeState::ParameterLayout MindballAudioProcessor::createParameterLayout()
{
juce::AudioProcessorValueTreeState::ParameterLayout layout;
layout.add (std::make_unique<juce::AudioParameterFloat> (
ParameterIDs::feedback, "Feedback",
juce::NormalisableRange<float> (0.0f, 1.0f, 0.0001f, 1.0f), 0.35f));
layout.add (std::make_unique<juce::AudioParameterFloat> (
ParameterIDs::locut, "Lo-Cut",
juce::NormalisableRange<float> (20.0f, 4000.0f, 1.0f, 0.3f), 100.0f));
layout.add (std::make_unique<juce::AudioParameterFloat> (
ParameterIDs::hicut, "Hi-Cut",
juce::NormalisableRange<float> (500.0f, 18000.0f, 1.0f, 0.3f), 12000.0f));
layout.add (std::make_unique<juce::AudioParameterFloat> (
ParameterIDs::time, "Time",
juce::NormalisableRange<float> (10.0f, 3000.0f, 1.0f, 0.4f), 300.0f));
layout.add (std::make_unique<juce::AudioParameterChoice> (
ParameterIDs::timeSync, "Time (Sync)",
juce::StringArray { "1/16", "1/8", "1/4", "1/3", "1/2", "3/4", "1", "3/2", "2", "3", "4", "8" }, 4));
layout.add (std::make_unique<juce::AudioParameterFloat> (
ParameterIDs::drywet, "Dry/Wet",
juce::NormalisableRange<float> (0.0f, 1.0f, 0.001f, 1.0f), 0.4f));
layout.add (std::make_unique<juce::AudioParameterBool> (ParameterIDs::sync, "Time Sync", false));
layout.add (std::make_unique<juce::AudioParameterChoice> (
ParameterIDs::mode, "Mode",
juce::StringArray { "Normal", "Invert", "Ping-Pong", "SuperPong" }, 0));
layout.add (std::make_unique<juce::AudioParameterBool> (ParameterIDs::doubleTap, "Double", false));
layout.add (std::make_unique<juce::AudioParameterBool> (ParameterIDs::center, "Center", false));
layout.add (std::make_unique<juce::AudioParameterBool> (ParameterIDs::autopan, "Auto-Pan", false));
layout.add (std::make_unique<juce::AudioParameterFloat> (
ParameterIDs::panDepth, "Pan Depth",
juce::NormalisableRange<float> (0.0f, 1.0f, 0.001f, 1.0f), 1.0f));
layout.add (std::make_unique<juce::AudioParameterBool> (ParameterIDs::bypass, "Bypass", false));
return layout;
}
const juce::String MindballAudioProcessor::getName() const { return JucePlugin_Name; }
bool MindballAudioProcessor::acceptsMidi() const { return false; }
bool MindballAudioProcessor::producesMidi() const { return false; }
double MindballAudioProcessor::getTailLengthSeconds() const { return 10.0; }
int MindballAudioProcessor::getNumPrograms() { return 1; }
int MindballAudioProcessor::getCurrentProgram() { return 0; }
void MindballAudioProcessor::setCurrentProgram (int) {}
const juce::String MindballAudioProcessor::getProgramName (int) { return {}; }
void MindballAudioProcessor::changeProgramName (int, const juce::String&) {}
void MindballAudioProcessor::prepareToPlay (double sampleRate, int /*samplesPerBlock*/)
{
engine.prepare (sampleRate, maxDelaySeconds);
meterDecay = static_cast<float> (std::exp (-1.0 / (0.4 * sampleRate)));
}
void MindballAudioProcessor::releaseResources()
{
engine.clear();
}
bool MindballAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const
{
const auto mainIn = layouts.getMainInputChannelSet();
const auto mainOut = layouts.getMainOutputChannelSet();
return mainIn == mainOut
&& (mainIn == juce::AudioChannelSet::mono() || mainIn == juce::AudioChannelSet::stereo());
}
void MindballAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer&)
{
juce::ScopedNoDenormals noDenormals;
const int numSamples = buffer.getNumSamples();
const int numChannels = buffer.getNumChannels();
if (numSamples == 0 || numChannels == 0)
return;
const bool bypass = *apvts.getRawParameterValue (ParameterIDs::bypass) > 0.5f;
if (bypass)
{
if (!wasBypassed)
{
engine.clear();
wasBypassed = true;
}
meterDryL = meterDryR = meterWetL = meterWetR = 0.0f;
dryMeterL.store (0.0f); dryMeterR.store (0.0f);
wetMeterL.store (0.0f); wetMeterR.store (0.0f);
return;
}
wasBypassed = false;
double bpm = 120.0;
if (auto* playHead = getPlayHead())
{
juce::AudioPlayHead::CurrentPositionInfo pos;
if (playHead->getCurrentPosition (pos))
bpm = pos.bpm > 0 ? pos.bpm : 120.0;
}
mindball::DelayEngine::Params p;
p.feedback = *apvts.getRawParameterValue (ParameterIDs::feedback);
p.loCutHz = *apvts.getRawParameterValue (ParameterIDs::locut);
p.hiCutHz = *apvts.getRawParameterValue (ParameterIDs::hicut);
p.timeMs = *apvts.getRawParameterValue (ParameterIDs::time);
p.timeSyncIndex = static_cast<int> (*apvts.getRawParameterValue (ParameterIDs::timeSync));
p.dryWet = *apvts.getRawParameterValue (ParameterIDs::drywet);
p.mode = static_cast<int> (*apvts.getRawParameterValue (ParameterIDs::mode));
p.sync = *apvts.getRawParameterValue (ParameterIDs::sync) > 0.5f;
p.doubleTap = *apvts.getRawParameterValue (ParameterIDs::doubleTap) > 0.5f;
p.center = *apvts.getRawParameterValue (ParameterIDs::center) > 0.5f;
p.autoPan = *apvts.getRawParameterValue (ParameterIDs::autopan) > 0.5f;
p.panDepth = *apvts.getRawParameterValue (ParameterIDs::panDepth);
p.bpm = bpm;
wetScratch.setSize (2, numSamples);
if (numChannels >= 2)
{
engine.process (buffer.getReadPointer (0), buffer.getReadPointer (1),
buffer.getWritePointer (0), buffer.getWritePointer (1),
numSamples, p,
wetScratch.getWritePointer (0), wetScratch.getWritePointer (1));
}
else
{
engine.process (buffer.getReadPointer (0), buffer.getReadPointer (0),
buffer.getWritePointer (0), buffer.getWritePointer (0),
numSamples, p,
wetScratch.getWritePointer (0), wetScratch.getWritePointer (1));
}
const float* outL = buffer.getReadPointer (0);
const float* outR = numChannels >= 2 ? buffer.getReadPointer (1) : outL;
const float* wetL = wetScratch.getReadPointer (0);
const float* wetR = wetScratch.getReadPointer (1);
for (int i = 0; i < numSamples; ++i)
{
const float dryL = outL[i] - wetL[i];
const float dryR = outR[i] - wetR[i];
meterDryL = std::max (std::abs (dryL), meterDryL * meterDecay);
meterDryR = std::max (std::abs (dryR), meterDryR * meterDecay);
meterWetL = std::max (std::abs (wetL[i]), meterWetL * meterDecay);
meterWetR = std::max (std::abs (wetR[i]), meterWetR * meterDecay);
}
dryMeterL.store (meterDryL); dryMeterR.store (meterDryR);
wetMeterL.store (meterWetL); wetMeterR.store (meterWetR);
}
juce::AudioProcessorEditor* MindballAudioProcessor::createEditor()
{
return new MindballAudioProcessorEditor (*this);
}
bool MindballAudioProcessor::hasEditor() const { return true; }
void MindballAudioProcessor::getStateInformation (juce::MemoryBlock& destData)
{
auto state = apvts.copyState();
std::unique_ptr<juce::XmlElement> xml (state.createXml());
copyXmlToBinary (*xml, destData);
}
void MindballAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
{
std::unique_ptr<juce::XmlElement> xml (getXmlFromBinary (data, sizeInBytes));
if (xml != nullptr)
apvts.replaceState (juce::ValueTree::fromXml (*xml));
}
juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter()
{
return new MindballAudioProcessor();
}