#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 ("lowCut", "Low Cut", juce::NormalisableRange (20.0f, 500.0f, 0.0f, 0.35f), 40.0f)); layout.add (std::make_unique ("highCut", "High Cut", juce::NormalisableRange (800.0f, 20000.0f, 0.0f, 0.25f), 14000.0f)); layout.add (std::make_unique ("decay", "Decay", juce::NormalisableRange (0.2f, 10.0f, 0.0f, 0.5f), 3.5f)); layout.add (std::make_unique ("size", "Size", juce::NormalisableRange (0.4f, 2.0f, 0.0f, 0.5f), 1.0f)); layout.add (std::make_unique ("diffusion", "Diffusion", juce::NormalisableRange (0.0f, 1.0f, 0.0f), 0.6f)); layout.add (std::make_unique ("brightness", "Brightness", juce::NormalisableRange (0.0f, 1.0f, 0.0f), 0.75f)); layout.add (std::make_unique ("feedback", "Feedback", juce::NormalisableRange (0.0f, 1.0f, 0.0f), 0.6f)); layout.add (std::make_unique ("pitch", "Pitch", juce::NormalisableRange (-12.0f, 12.0f, 0.0f), 0.0f)); layout.add (std::make_unique ("dryWet", "Dry/Wet", juce::NormalisableRange (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& 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; 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(); dryScratch[(size_t) s] = inL; } } 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 xml (state.createXml()); copyXmlToBinary (*xml, destData); } void HorizontAudioProcessor::setStateInformation (const void* data, int sizeInBytes) { std::unique_ptr xml (getXmlFromBinary (data, sizeInBytes)); if (xml != nullptr) apvts.replaceState (juce::ValueTree::fromXml (*xml)); updateParameters(); } juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() { return new HorizontAudioProcessor(); }