mirror of
https://codeberg.org/armin/horizont.git
synced 2026-09-01 04:10:46 +02:00
316 lines
9.3 KiB
C++
316 lines
9.3 KiB
C++
#include "PluginProcessor.h"
|
|
|
|
#include "PluginEditor.h"
|
|
|
|
HorizontAudioProcessor::HorizontAudioProcessor()
|
|
: AudioProcessor (BusesProperties()
|
|
.withInput ("Input", juce::AudioChannelSet::stereo(), true)
|
|
.withOutput ("Output", juce::AudioChannelSet::stereo(), true)),
|
|
apvts (*this, nullptr, "Parameters", createParameterLayout()),
|
|
presets (getPresets())
|
|
{
|
|
lowCutParam = apvts.getRawParameterValue ("lowCut");
|
|
highCutParam = apvts.getRawParameterValue ("highCut");
|
|
decayParam = apvts.getRawParameterValue ("decay");
|
|
sizeParam = apvts.getRawParameterValue ("size");
|
|
diffusionParam = apvts.getRawParameterValue ("diffusion");
|
|
brightnessParam = apvts.getRawParameterValue ("brightness");
|
|
feedbackParam = apvts.getRawParameterValue ("feedback");
|
|
pitchParam = apvts.getRawParameterValue ("pitch");
|
|
dryWetParam = apvts.getRawParameterValue ("dryWet");
|
|
}
|
|
|
|
HorizontAudioProcessor::~HorizontAudioProcessor()
|
|
{
|
|
}
|
|
|
|
juce::AudioProcessorValueTreeState::ParameterLayout HorizontAudioProcessor::createParameterLayout()
|
|
{
|
|
juce::AudioProcessorValueTreeState::ParameterLayout layout;
|
|
|
|
layout.add (std::make_unique<juce::AudioParameterFloat> ("lowCut", "Low Cut",
|
|
juce::NormalisableRange<float> (20.0f, 500.0f, 0.0f, 0.35f), 40.0f));
|
|
|
|
layout.add (std::make_unique<juce::AudioParameterFloat> ("highCut", "High Cut",
|
|
juce::NormalisableRange<float> (800.0f, 20000.0f, 0.0f, 0.25f), 14000.0f));
|
|
|
|
layout.add (std::make_unique<juce::AudioParameterFloat> ("decay", "Decay",
|
|
juce::NormalisableRange<float> (0.2f, 10.0f, 0.0f, 0.5f), 3.5f));
|
|
|
|
layout.add (std::make_unique<juce::AudioParameterFloat> ("size", "Size",
|
|
juce::NormalisableRange<float> (0.4f, 2.0f, 0.0f, 0.5f), 1.0f));
|
|
|
|
layout.add (std::make_unique<juce::AudioParameterFloat> ("diffusion", "Diffusion",
|
|
juce::NormalisableRange<float> (0.0f, 1.0f, 0.0f), 0.6f));
|
|
|
|
layout.add (std::make_unique<juce::AudioParameterFloat> ("brightness", "Brightness",
|
|
juce::NormalisableRange<float> (0.0f, 1.0f, 0.0f), 0.75f));
|
|
|
|
layout.add (std::make_unique<juce::AudioParameterFloat> ("feedback", "Feedback",
|
|
juce::NormalisableRange<float> (0.0f, 1.0f, 0.0f), 0.6f));
|
|
|
|
layout.add (std::make_unique<juce::AudioParameterFloat> ("pitch", "Pitch",
|
|
juce::NormalisableRange<float> (-12.0f, 12.0f, 0.0f), 0.0f));
|
|
|
|
layout.add (std::make_unique<juce::AudioParameterFloat> ("dryWet", "Dry/Wet",
|
|
juce::NormalisableRange<float> (0.0f, 1.0f, 0.0f), 0.35f));
|
|
|
|
return layout;
|
|
}
|
|
|
|
void HorizontAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
|
|
{
|
|
wetScratch.resize ((size_t) juce::jmax (samplesPerBlock, 1));
|
|
dryScratch.resize ((size_t) juce::jmax (samplesPerBlock, 1));
|
|
wetFifo.reset();
|
|
engine.prepare (sampleRate);
|
|
updateParameters();
|
|
}
|
|
|
|
void HorizontAudioProcessor::releaseResources()
|
|
{
|
|
engine.reset();
|
|
}
|
|
|
|
bool HorizontAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const
|
|
{
|
|
if (layouts.getMainOutputChannelSet().isDisabled())
|
|
return false;
|
|
|
|
const int outCh = layouts.getMainOutputChannelSet().size();
|
|
const int inCh = layouts.getMainInputChannelSet().size();
|
|
|
|
if (inCh == 0)
|
|
return outCh <= 2;
|
|
|
|
return inCh <= 2 && outCh <= 2;
|
|
}
|
|
|
|
void HorizontAudioProcessor::updateParameters()
|
|
{
|
|
engine.setLowCut (lowCutParam->load());
|
|
engine.setHighCut (highCutParam->load());
|
|
engine.setDecay (decayParam->load());
|
|
engine.setSize (sizeParam->load());
|
|
engine.setDiffusion (diffusionParam->load());
|
|
engine.setBrightness (brightnessParam->load());
|
|
engine.setFeedback (feedbackParam->load());
|
|
engine.setPitch (pitchParam->load());
|
|
engine.setDryWet (dryWetParam->load());
|
|
}
|
|
|
|
void HorizontAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer&)
|
|
{
|
|
juce::ScopedNoDenormals noDenormals;
|
|
|
|
const int numSamples = buffer.getNumSamples();
|
|
if (numSamples == 0)
|
|
return;
|
|
|
|
const int numOut = juce::jmin (buffer.getNumChannels(), 2);
|
|
if (numOut == 0)
|
|
return;
|
|
|
|
for (int ch = numOut; ch < buffer.getNumChannels(); ++ch)
|
|
buffer.clear (ch, 0, numSamples);
|
|
|
|
updateParameters();
|
|
|
|
const int inputChannels = getChannelCountOfBus (true, 0);
|
|
const bool haveLeft = buffer.getNumChannels() > 0 && inputChannels > 0;
|
|
const bool haveRight = buffer.getNumChannels() > 1 && inputChannels > 1;
|
|
const bool canCaptureWet = wetScratch.size() >= (size_t) numSamples;
|
|
const float wetVal = dryWetParam->load();
|
|
const float dryGain = 1.0f - wetVal;
|
|
|
|
for (int s = 0; s < numSamples; ++s)
|
|
{
|
|
const float inL = haveLeft ? buffer.getSample (0, s) : 0.0f;
|
|
const float inR = haveRight ? buffer.getSample (1, s) : inL;
|
|
|
|
float outL = 0.0f;
|
|
float outR = 0.0f;
|
|
engine.processSample (inL, inR, outL, outR);
|
|
|
|
buffer.setSample (0, s, outL);
|
|
if (numOut > 1)
|
|
buffer.setSample (1, s, outR);
|
|
|
|
if (canCaptureWet)
|
|
{
|
|
wetScratch[(size_t) s] = engine.getLastWetL() * wetVal;
|
|
dryScratch[(size_t) s] = inL * dryGain;
|
|
}
|
|
}
|
|
|
|
if (canCaptureWet)
|
|
{
|
|
pushWetSamples (wetScratch.data(), numSamples);
|
|
appendDrySamples (dryScratch.data(), numSamples);
|
|
}
|
|
}
|
|
|
|
void HorizontAudioProcessor::pushWetSamples (const float* src, int numToWrite)
|
|
{
|
|
if (numToWrite <= 0)
|
|
return;
|
|
|
|
numToWrite = juce::jmin (numToWrite, wetFifo.getFreeSpace());
|
|
if (numToWrite <= 0)
|
|
return;
|
|
|
|
int i = 0;
|
|
auto handle = wetFifo.write (numToWrite);
|
|
handle.forEach ([&] (int idx) { wetRing.setSample (0, idx, src[i++]); });
|
|
}
|
|
|
|
int HorizontAudioProcessor::readWetSamples (float* dest, int numToRead)
|
|
{
|
|
const int avail = wetFifo.getNumReady();
|
|
if (avail <= 0)
|
|
return 0;
|
|
|
|
numToRead = juce::jmin (numToRead, avail);
|
|
|
|
int i = 0;
|
|
auto handle = wetFifo.read (numToRead);
|
|
handle.forEach ([&] (int idx) { dest[i++] = wetRing.getSample (0, idx); });
|
|
|
|
return numToRead;
|
|
}
|
|
|
|
void HorizontAudioProcessor::appendDrySamples (const float* src, int numToWrite)
|
|
{
|
|
if (numToWrite <= 0)
|
|
return;
|
|
|
|
size_t w = dryWritePos.load (std::memory_order_relaxed);
|
|
for (int i = 0; i < numToWrite; ++i)
|
|
{
|
|
dryRingLive[w] = src[i];
|
|
w = (w + 1) % (size_t) dryRingSize;
|
|
}
|
|
dryWritePos.store (w, std::memory_order_release);
|
|
dryTotalWritten.fetch_add ((size_t) numToWrite, std::memory_order_release);
|
|
}
|
|
|
|
int HorizontAudioProcessor::copyRecentDrySamples (float* dest, int numToRead)
|
|
{
|
|
numToRead = juce::jmin (numToRead, dryRingSize);
|
|
|
|
const size_t written = dryTotalWritten.load (std::memory_order_acquire);
|
|
const size_t wp = dryWritePos.load (std::memory_order_acquire);
|
|
|
|
if (written < (size_t) numToRead)
|
|
{
|
|
const int avail = (int) written;
|
|
const int lead = numToRead - avail;
|
|
std::fill (dest, dest + lead, 0.0f);
|
|
for (int i = 0; i < avail; ++i)
|
|
{
|
|
const size_t idx = (wp + (size_t) dryRingSize - (size_t) avail + (size_t) i) % (size_t) dryRingSize;
|
|
dest[lead + i] = dryRingLive[idx];
|
|
}
|
|
return numToRead;
|
|
}
|
|
|
|
for (int i = 0; i < numToRead; ++i)
|
|
{
|
|
const size_t idx = (wp + (size_t) dryRingSize - (size_t) numToRead + (size_t) i) % (size_t) dryRingSize;
|
|
dest[i] = dryRingLive[idx];
|
|
}
|
|
|
|
return numToRead;
|
|
}
|
|
|
|
juce::AudioProcessorEditor* HorizontAudioProcessor::createEditor()
|
|
{
|
|
return new HorizontAudioProcessorEditor (*this, apvts);
|
|
}
|
|
|
|
bool HorizontAudioProcessor::hasEditor() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
const juce::String HorizontAudioProcessor::getName() const
|
|
{
|
|
return "Horizont";
|
|
}
|
|
|
|
bool HorizontAudioProcessor::acceptsMidi() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool HorizontAudioProcessor::producesMidi() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
double HorizontAudioProcessor::getTailLengthSeconds() const
|
|
{
|
|
return 10.0;
|
|
}
|
|
|
|
int HorizontAudioProcessor::getNumPrograms()
|
|
{
|
|
return (int) presets.size();
|
|
}
|
|
|
|
int HorizontAudioProcessor::getCurrentProgram()
|
|
{
|
|
return currentProgram;
|
|
}
|
|
|
|
void HorizontAudioProcessor::setCurrentProgram (int index)
|
|
{
|
|
if (index >= 0 && index < (int) presets.size())
|
|
{
|
|
currentProgram = index;
|
|
applyPreset (presets[(size_t) index]);
|
|
}
|
|
}
|
|
|
|
const juce::String HorizontAudioProcessor::getProgramName (int index)
|
|
{
|
|
if (index >= 0 && index < (int) presets.size())
|
|
return presets[(size_t) index].name;
|
|
|
|
return "Default";
|
|
}
|
|
|
|
void HorizontAudioProcessor::changeProgramName (int, const juce::String&)
|
|
{
|
|
}
|
|
|
|
void HorizontAudioProcessor::applyPreset (const Preset& preset)
|
|
{
|
|
for (const auto& v : preset.values)
|
|
{
|
|
if (auto* param = apvts.getParameter (v.id))
|
|
param->setValueNotifyingHost (param->convertTo0to1 (v.value));
|
|
}
|
|
}
|
|
|
|
void HorizontAudioProcessor::getStateInformation (juce::MemoryBlock& destData)
|
|
{
|
|
auto state = apvts.copyState();
|
|
std::unique_ptr<juce::XmlElement> xml (state.createXml());
|
|
copyXmlToBinary (*xml, destData);
|
|
}
|
|
|
|
void HorizontAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
|
|
{
|
|
std::unique_ptr<juce::XmlElement> xml (getXmlFromBinary (data, sizeInBytes));
|
|
|
|
if (xml != nullptr)
|
|
apvts.replaceState (juce::ValueTree::fromXml (*xml));
|
|
|
|
updateParameters();
|
|
}
|
|
|
|
juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter()
|
|
{
|
|
return new HorizontAudioProcessor();
|
|
}
|