diff --git a/resources/Delay.h b/resources/Delay.h index 7daf224..c777ca6 100644 --- a/resources/Delay.h +++ b/resources/Delay.h @@ -47,9 +47,9 @@ class Delay : private ProcessorBase { public: - Delay() {}; + Delay() {} - ~Delay() override {} ; + ~Delay() override {} void setDelayTime (float delayTimeInSeconds) { @@ -67,18 +67,25 @@ class Delay : private ProcessorBase prepare(spec); } - const int getDelayInSamples() + unsigned int getDelayInSamples() { - return bypassed ? 0 : delayInSamples; + if (bypassed) + { + return 0; + } + else + { + return delayInSamples; + } } void prepare (const ProcessSpec& specs) override { spec = specs; - delayInSamples = roundToInt(delay * specs.sampleRate); + delayInSamples = static_cast (roundToInt (delay * specs.sampleRate)); - buffer.setSize(specs.numChannels, specs.maximumBlockSize + delayInSamples); + buffer.setSize(static_cast (specs.numChannels), static_cast (specs.maximumBlockSize + delayInSamples)); buffer.clear(); writePosition = 0; } @@ -101,22 +108,22 @@ class Delay : private ProcessorBase getReadWritePositions(false, (int) L, startIndex, blockSize1, blockSize2); for (int ch = 0; ch < nCh; ch++) - buffer.copyFrom(ch, startIndex, abIn.getChannelPointer(ch), blockSize1); + buffer.copyFrom(ch, startIndex, abIn.getChannelPointer(static_cast (ch)), blockSize1); if (blockSize2 > 0) for (int ch = 0; ch < nCh; ch++) - buffer.copyFrom(ch, 0, abIn.getChannelPointer(ch) + blockSize1, blockSize2); + buffer.copyFrom(ch, 0, abIn.getChannelPointer(static_cast (ch)) + blockSize1, blockSize2); // read from delay line getReadWritePositions(true, (int) L, startIndex, blockSize1, blockSize2); for (int ch = 0; ch < nCh; ch++) - FloatVectorOperations::copy(abOut.getChannelPointer(ch), buffer.getReadPointer(ch) + startIndex, blockSize1); + FloatVectorOperations::copy(abOut.getChannelPointer(static_cast (ch)), buffer.getReadPointer(ch) + startIndex, blockSize1); if (blockSize2 > 0) for (int ch = 0; ch < nCh; ch++) - FloatVectorOperations::copy(abOut.getChannelPointer(ch) + blockSize1, buffer.getReadPointer(ch), blockSize2); + FloatVectorOperations::copy(abOut.getChannelPointer(static_cast (ch)) + blockSize1, buffer.getReadPointer(ch), blockSize2); writePosition += L; @@ -135,7 +142,7 @@ class Delay : private ProcessorBase int pos = writePosition; if (read) { - pos = writePosition - delayInSamples; + pos = static_cast (static_cast (writePosition) - delayInSamples); } if (pos < 0) pos = pos + L; diff --git a/source/PluginEditor.cpp b/source/PluginEditor.cpp index 73a21fa..95d73f4 100644 --- a/source/PluginEditor.cpp +++ b/source/PluginEditor.cpp @@ -352,6 +352,7 @@ PolarDesignerAudioProcessorEditor::PolarDesignerAudioProcessorEditor (PolarDesig zeroDelayModeChange(); addAndMakeVisible(&trimSlider); + trimSlider.addListener(this); addAndMakeVisible(&tbTrimSliderCenterPointer); tbTrimSliderCenterPointer.setButtonText("Trim Slider Pointer"); diff --git a/source/PluginProcessor.cpp b/source/PluginProcessor.cpp index 42a2202..df291c5 100644 --- a/source/PluginProcessor.cpp +++ b/source/PluginProcessor.cpp @@ -40,6 +40,9 @@ along with this program. If not, see . /* We use versionHint of ParameterID from now on - rigorously! */ #define PD_PARAMETER_V1 1 + /* Trim Slider */ +#define PD_PARAMETER_V2 2 + //============================================================================== PolarDesignerAudioProcessor::PolarDesignerAudioProcessor() : AudioProcessor (BusesProperties() .withInput ("Input", AudioChannelSet::stereo(), true) @@ -53,7 +56,12 @@ PolarDesignerAudioProcessor::PolarDesignerAudioProcessor() : AudioProcessor (Bus nBands(5), vtsParams(*this, &undoManager, "AAPolarDesigner", { - std::make_unique (ParameterID {"xOverF1", PD_PARAMETER_V1}, "Xover1", NormalisableRange(0.0f, 1.0f, 0.0001f), + std::make_unique (ParameterID {"trimPosition", PD_PARAMETER_V2}, "trimPosition", NormalisableRange(0.0f, 1.0f, 0.0001f), + 0, "", + AudioProcessorParameter::genericParameter, + [&](float value, [[maybe_unused]]int maximumStringLength) {return String(std::roundf(hzFromZeroToOne(0, value))) + " trimPot";}, + nullptr), + std::make_unique (ParameterID {"xOverF1", PD_PARAMETER_V1}, "Xover1", NormalisableRange(0.0f, 1.0f, 0.0001f), hzToZeroToOne(0, INIT_XOVER_FREQS_5B[0]), "", AudioProcessorParameter::genericParameter, [&](float value, [[maybe_unused]]int maximumStringLength) {return String(std::roundf(hzFromZeroToOne(0, value))) + " Hz";}, @@ -146,1438 +154,1458 @@ PolarDesignerAudioProcessor::PolarDesignerAudioProcessor() : AudioProcessor (Bus trackingDisturber(false), disturberRecorded(false), signalRecorded(false), - currentSampleRate(48000) -{ - - vtsParams.addParameterListener("xOverF1", this); - vtsParams.addParameterListener("xOverF2", this); - vtsParams.addParameterListener("xOverF3", this); - vtsParams.addParameterListener("xOverF4", this); - xOverFreqs[0] = vtsParams.getRawParameterValue("xOverF1"); - xOverFreqs[1] = vtsParams.getRawParameterValue("xOverF2"); - xOverFreqs[2] = vtsParams.getRawParameterValue("xOverF3"); - xOverFreqs[3] = vtsParams.getRawParameterValue("xOverF4"); - for (int i = 0; i < 5; ++i) - { - vtsParams.addParameterListener("alpha"+String(i+1), this); - dirFactors[i] = vtsParams.getRawParameterValue("alpha"+String(i+1)); - - vtsParams.addParameterListener("solo"+String(i+1), this); - soloBand[i] = vtsParams.getRawParameterValue("solo"+String(i+1)); - - vtsParams.addParameterListener("mute"+String(i+1), this); - muteBand[i] = vtsParams.getRawParameterValue("mute"+String(i+1)); - - vtsParams.addParameterListener("gain"+String(i+1), this); - bandGains[i] = vtsParams.getRawParameterValue("gain"+String(i+1)); - } - vtsParams.addParameterListener("nrBands", this); - nBandsPtr = vtsParams.getRawParameterValue("nrBands"); - vtsParams.addParameterListener("allowBackwardsPattern", this); - allowBackwardsPattern = vtsParams.getRawParameterValue("allowBackwardsPattern"); - vtsParams.addParameterListener("proximity", this); - proxDistance = vtsParams.getRawParameterValue("proximity"); - vtsParams.addParameterListener("proximityOnOff", this); - proxOnOff = vtsParams.getRawParameterValue("proximityOnOff"); - vtsParams.addParameterListener("zeroDelayMode", this); - zeroDelayMode = vtsParams.getRawParameterValue("zeroDelayMode"); - vtsParams.addParameterListener("syncChannel", this); - syncChannelPtr = vtsParams.getRawParameterValue("syncChannel"); - - // properties file: saves user preset folder location - PropertiesFile::Options options; - options.applicationName = "PolarDesigner"; - options.filenameSuffix = "settings"; - options.folderName = "AustrianAudio"; - options.osxLibrarySubFolder = "Preferences"; - - properties = std::unique_ptr(new PropertiesFile (options)); - lastDir = File(properties->getValue ("presetFolder")); - - dfEqOmniBuffer.copyFrom(0, 0, DFEQ_COEFFS_OMNI, DF_EQ_LEN); - dfEqEightBuffer.copyFrom(0, 0, DFEQ_COEFFS_EIGHT, DF_EQ_LEN); - ffEqOmniBuffer.copyFrom(0, 0, FFEQ_COEFFS_OMNI, FF_EQ_LEN); - ffEqEightBuffer.copyFrom(0, 0, FFEQ_COEFFS_EIGHT, FF_EQ_LEN); - - updateLatency(); - delay.setDelayTime (std::ceilf(static_cast(FILTER_BANK_IR_LENGTH_AT_NATIVE_SAMPLE_RATE) / 2 - 1) / FILTER_BANK_NATIVE_SAMPLE_RATE); - - oldProxDistance = proxDistance->load(); - - termControlWaveform.setRepaintRate(30); - termControlWaveform.setBufferSize(256); - - startTimer(50); -} - -PolarDesignerAudioProcessor::~PolarDesignerAudioProcessor() -{ -} + currentSampleRate (48000) + { + vtsParams.addParameterListener ("trimPosition", this); + + vtsParams.addParameterListener ("xOverF1", this); + vtsParams.addParameterListener ("xOverF2", this); + vtsParams.addParameterListener ("xOverF3", this); + vtsParams.addParameterListener ("xOverF4", this); + + // !J! Todo: initialize trimPosition + trimPosition = vtsParams.getRawParameterValue ("trimPosition"); + + xOverFreqs[0] = vtsParams.getRawParameterValue ("xOverF1"); + xOverFreqs[1] = vtsParams.getRawParameterValue ("xOverF2"); + xOverFreqs[2] = vtsParams.getRawParameterValue ("xOverF3"); + xOverFreqs[3] = vtsParams.getRawParameterValue ("xOverF4"); + for (int i = 0; i < 5; ++i) + { + vtsParams.addParameterListener ("alpha" + String (i + 1), this); + dirFactors[i] = vtsParams.getRawParameterValue ("alpha" + String (i + 1)); + + vtsParams.addParameterListener ("solo" + String (i + 1), this); + soloBand[i] = vtsParams.getRawParameterValue ("solo" + String (i + 1)); + + vtsParams.addParameterListener ("mute" + String (i + 1), this); + muteBand[i] = vtsParams.getRawParameterValue ("mute" + String (i + 1)); + + vtsParams.addParameterListener ("gain" + String (i + 1), this); + bandGains[i] = vtsParams.getRawParameterValue ("gain" + String (i + 1)); + } + vtsParams.addParameterListener ("nrBands", this); + nBandsPtr = vtsParams.getRawParameterValue ("nrBands"); + vtsParams.addParameterListener ("allowBackwardsPattern", this); + allowBackwardsPattern = vtsParams.getRawParameterValue ("allowBackwardsPattern"); + vtsParams.addParameterListener ("proximity", this); + proxDistance = vtsParams.getRawParameterValue ("proximity"); + vtsParams.addParameterListener ("proximityOnOff", this); + proxOnOff = vtsParams.getRawParameterValue ("proximityOnOff"); + vtsParams.addParameterListener ("zeroDelayMode", this); + zeroDelayMode = vtsParams.getRawParameterValue ("zeroDelayMode"); + vtsParams.addParameterListener ("syncChannel", this); + syncChannelPtr = vtsParams.getRawParameterValue ("syncChannel"); + + // properties file: saves user preset folder location + PropertiesFile::Options options; + options.applicationName = "PolarDesigner"; + options.filenameSuffix = "settings"; + options.folderName = "AustrianAudio"; + options.osxLibrarySubFolder = "Preferences"; + + properties = std::unique_ptr (new PropertiesFile (options)); + lastDir = File (properties->getValue ("presetFolder")); + + dfEqOmniBuffer.copyFrom (0, 0, DFEQ_COEFFS_OMNI, DF_EQ_LEN); + dfEqEightBuffer.copyFrom (0, 0, DFEQ_COEFFS_EIGHT, DF_EQ_LEN); + ffEqOmniBuffer.copyFrom (0, 0, FFEQ_COEFFS_OMNI, FF_EQ_LEN); + ffEqEightBuffer.copyFrom (0, 0, FFEQ_COEFFS_EIGHT, FF_EQ_LEN); + + updateLatency(); + delay.setDelayTime (std::ceilf (static_cast (FILTER_BANK_IR_LENGTH_AT_NATIVE_SAMPLE_RATE) / 2 - 1) / FILTER_BANK_NATIVE_SAMPLE_RATE); + + oldProxDistance = proxDistance->load(); + + termControlWaveform.setRepaintRate (30); + termControlWaveform.setBufferSize (256); + + startTimer (50); + } -//============================================================================== -const String PolarDesignerAudioProcessor::getName() const -{ - return JucePlugin_Name; -} + PolarDesignerAudioProcessor::~PolarDesignerAudioProcessor() + { + } -bool PolarDesignerAudioProcessor::acceptsMidi() const -{ + //============================================================================== + const String PolarDesignerAudioProcessor::getName() const + { + return JucePlugin_Name; + } + + bool PolarDesignerAudioProcessor::acceptsMidi() const + { #if JucePlugin_WantsMidiInput - return true; + return true; #else - return false; + return false; #endif -} + } -bool PolarDesignerAudioProcessor::producesMidi() const -{ + bool PolarDesignerAudioProcessor::producesMidi() const + { #if JucePlugin_ProducesMidiOutput - return true; + return true; #else - return false; + return false; #endif -} + } -bool PolarDesignerAudioProcessor::isMidiEffect() const -{ + bool PolarDesignerAudioProcessor::isMidiEffect() const + { #if JucePlugin_IsMidiEffect - return true; + return true; #else - return false; + return false; #endif -} + } + + double PolarDesignerAudioProcessor::getTailLengthSeconds() const + { + return 0.0; + } + + int PolarDesignerAudioProcessor::getNumPrograms() + { + return 1; // NB: some hosts don't cope very well if you tell them there are 0 programs, + // so this should be at least 1, even if you're not really implementing programs. + } + + int PolarDesignerAudioProcessor::getCurrentProgram() + { + return 0; + } -double PolarDesignerAudioProcessor::getTailLengthSeconds() const -{ - return 0.0; -} + void PolarDesignerAudioProcessor::setCurrentProgram ([[maybe_unused]] int index) + { + } -int PolarDesignerAudioProcessor::getNumPrograms() -{ - return 1; // NB: some hosts don't cope very well if you tell them there are 0 programs, - // so this should be at least 1, even if you're not really implementing programs. -} + const String PolarDesignerAudioProcessor::getProgramName ([[maybe_unused]] int index) + { + return {}; + } -int PolarDesignerAudioProcessor::getCurrentProgram() -{ - return 0; -} + void PolarDesignerAudioProcessor::changeProgramName ([[maybe_unused]] int index, [[maybe_unused]] const String& newName) + { + } + + //============================================================================== + void PolarDesignerAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) + { + if (!doublesEquivalent (sampleRate, currentSampleRate)) + { + firLen = static_cast (std::ceil (static_cast (FILTER_BANK_IR_LENGTH_AT_NATIVE_SAMPLE_RATE) / FILTER_BANK_NATIVE_SAMPLE_RATE * sampleRate)); + if (firLen % 2 == 0) // make sure firLen is odd + firLen++; + + updateLatency(); + } + + currentBlockSize = samplesPerBlock; + currentSampleRate = sampleRate; + + dsp::ProcessSpec delaySpec { currentSampleRate, static_cast (currentBlockSize), 1 }; + delay.prepare (delaySpec); + delayBuffer.clear(); + delayBuffer.setSize (1, currentBlockSize); + + // filter bank + filterBankBuffer.setSize (N_CH_IN * 5, currentBlockSize); + filterBankBuffer.clear(); + firFilterBuffer.setSize (5, firLen); + firFilterBuffer.clear(); + omniEightBuffer.setSize (2, currentBlockSize); + omniEightBuffer.clear(); + + computeAllFilterCoefficients(); + initAllConvolvers(); + for (auto& conv : convolvers) + { + conv.reset(); + } + + // diffuse field eq + dsp::ProcessSpec eqSpec { currentSampleRate, static_cast (currentBlockSize), 1 }; + dfEqOmniConv.prepare (eqSpec); // must be called before loading an ir + dfEqOmniConv.loadImpulseResponse (std::move (dfEqOmniBuffer), EQ_SAMPLE_RATE, dsp::Convolution::Stereo::no, dsp::Convolution::Trim::no, dsp::Convolution::Normalise::no); + + dsp::ProcessSpec eqSpec2 { currentSampleRate, static_cast (currentBlockSize), 1 }; + dfEqEightConv.prepare (eqSpec2); + dfEqOmniConv.loadImpulseResponse (std::move (dfEqEightBuffer), EQ_SAMPLE_RATE, dsp::Convolution::Stereo::no, dsp::Convolution::Trim::no, dsp::Convolution::Normalise::no); + + dsp::ProcessSpec eqSpec3 { currentSampleRate, static_cast (currentBlockSize), 1 }; + ffEqOmniConv.prepare (eqSpec3); // must be called before loading an ir + dfEqOmniConv.loadImpulseResponse (std::move (ffEqOmniBuffer), EQ_SAMPLE_RATE, dsp::Convolution::Stereo::no, dsp::Convolution::Trim::no, dsp::Convolution::Normalise::no); + + dsp::ProcessSpec eqSpec4 { currentSampleRate, static_cast (currentBlockSize), 1 }; + ffEqEightConv.prepare (eqSpec4); + dfEqOmniConv.loadImpulseResponse (std::move (dfEqEightBuffer), EQ_SAMPLE_RATE, dsp::Convolution::Stereo::no, dsp::Convolution::Trim::no, dsp::Convolution::Normalise::no); + + dfEqOmniConv.reset(); + dfEqEightConv.reset(); + ffEqOmniConv.reset(); + ffEqEightConv.reset(); + + for (int i = 0; i < 5; ++i) + { + oldDirFactors[i] = dirFactors[i]->load(); + oldBandGains[i] = bandGains[i]->load(); + } + + // proximity compensation IIR + dsp::ProcessSpec specProx { currentSampleRate, static_cast (currentBlockSize), 1 }; + proxCompIIR.prepare (specProx); + + proxCompIIR.reset(); + setProxCompCoefficients (proxDistance->load()); + } -void PolarDesignerAudioProcessor::setCurrentProgram ([[maybe_unused]]int index) -{ -} + void PolarDesignerAudioProcessor::releaseResources() + { + // When playback stops, you can use this as an opportunity to free up any + // spare memory, etc. + } -const String PolarDesignerAudioProcessor::getProgramName ([[maybe_unused]]int index) -{ - return {}; -} + bool PolarDesignerAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const + { + if ((layouts.getMainOutputChannelSet() != AudioChannelSet::mono() + && layouts.getMainOutputChannelSet() != AudioChannelSet::stereo()) + || layouts.getMainInputChannelSet() != AudioChannelSet::stereo()) + return false; -void PolarDesignerAudioProcessor::changeProgramName ([[maybe_unused]]int index, [[maybe_unused]]const String& newName) -{ -} + if (layouts.getMainInputChannelSet().isDisabled()) + return false; -//============================================================================== -void PolarDesignerAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) -{ - if (!doublesEquivalent(sampleRate, currentSampleRate)) - { - firLen = static_cast (std::ceil (static_cast (FILTER_BANK_IR_LENGTH_AT_NATIVE_SAMPLE_RATE) / FILTER_BANK_NATIVE_SAMPLE_RATE * sampleRate)); - if (firLen % 2 == 0) // make sure firLen is odd - firLen++; - - updateLatency(); - } - - currentBlockSize = samplesPerBlock; - currentSampleRate = sampleRate; - - dsp::ProcessSpec delaySpec {currentSampleRate, static_cast(currentBlockSize), 1}; - delay.prepare (delaySpec); - delayBuffer.clear(); - delayBuffer.setSize(1, currentBlockSize); - - // filter bank - filterBankBuffer.setSize(N_CH_IN * 5, currentBlockSize); - filterBankBuffer.clear(); - firFilterBuffer.setSize(5, firLen); - firFilterBuffer.clear(); - omniEightBuffer.setSize(2, currentBlockSize); - omniEightBuffer.clear(); - - computeAllFilterCoefficients(); - initAllConvolvers(); - for (auto &conv : convolvers) - { - conv.reset(); - } - - // diffuse field eq - dsp::ProcessSpec eqSpec {currentSampleRate, static_cast(currentBlockSize), 1}; - dfEqOmniConv.prepare (eqSpec); // must be called before loading an ir - dfEqOmniConv.loadImpulseResponse(std::move(dfEqOmniBuffer), EQ_SAMPLE_RATE, dsp::Convolution::Stereo::no, dsp::Convolution::Trim::no, dsp::Convolution::Normalise::no); - - dsp::ProcessSpec eqSpec2{ currentSampleRate, static_cast(currentBlockSize), 1 }; - dfEqEightConv.prepare (eqSpec2); - dfEqOmniConv.loadImpulseResponse(std::move(dfEqEightBuffer), EQ_SAMPLE_RATE, dsp::Convolution::Stereo::no, dsp::Convolution::Trim::no, dsp::Convolution::Normalise::no); - - dsp::ProcessSpec eqSpec3{ currentSampleRate, static_cast(currentBlockSize), 1 }; - ffEqOmniConv.prepare (eqSpec3); // must be called before loading an ir - dfEqOmniConv.loadImpulseResponse(std::move(ffEqOmniBuffer), EQ_SAMPLE_RATE, dsp::Convolution::Stereo::no, dsp::Convolution::Trim::no, dsp::Convolution::Normalise::no); - - dsp::ProcessSpec eqSpec4{ currentSampleRate, static_cast(currentBlockSize), 1 }; - ffEqEightConv.prepare (eqSpec4); - dfEqOmniConv.loadImpulseResponse(std::move(dfEqEightBuffer), EQ_SAMPLE_RATE, dsp::Convolution::Stereo::no, dsp::Convolution::Trim::no, dsp::Convolution::Normalise::no); - - dfEqOmniConv.reset(); - dfEqEightConv.reset(); - ffEqOmniConv.reset(); - ffEqEightConv.reset(); - - for (int i = 0; i < 5; ++i) - { - oldDirFactors[i] = dirFactors[i]->load(); - oldBandGains[i] = bandGains[i]->load(); - } - - // proximity compensation IIR - dsp::ProcessSpec specProx { currentSampleRate, static_cast (currentBlockSize), 1 }; - proxCompIIR.prepare(specProx); - - proxCompIIR.reset(); - setProxCompCoefficients(proxDistance->load()); - -} - -void PolarDesignerAudioProcessor::releaseResources() -{ - // When playback stops, you can use this as an opportunity to free up any - // spare memory, etc. -} - -bool PolarDesignerAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const -{ - if ((layouts.getMainOutputChannelSet() != AudioChannelSet::mono() - && layouts.getMainOutputChannelSet() != AudioChannelSet::stereo()) - || layouts.getMainInputChannelSet() != AudioChannelSet::stereo()) - return false; - - if (layouts.getMainInputChannelSet().isDisabled()) - return false; - - if (layouts.getMainOutputChannelSet().isDisabled()) - return false; - - return true; -} - -void PolarDesignerAudioProcessor::processBlock (AudioBuffer& buffer, [[maybe_unused]]MidiBuffer& midiMessages) -{ - ScopedNoDenormals noDenormals; - - if (isBypassed) { - isBypassed = false; - updateLatency(); - } - - int numSamples = buffer.getNumSamples(); - - // create omni and eight signals - createOmniAndEightSignals (buffer); - - // proximity compensation filter - auto proximity = (proxOnOff->load() == 1.f) ? proxDistance->load() : 0.f; - if (zeroDelayMode->load() < 0.5f && proximity < -0.05) // reduce proximity effect only on figure-of-eight - { - float* writePointerEight = omniEightBuffer.getWritePointer (1); - dsp::AudioBlock eightBlock(&writePointerEight, 1, static_cast (numSamples)); - dsp::ProcessContextReplacing contextProxEight(eightBlock); - proxCompIIR.process(contextProxEight); - } - else if (zeroDelayMode->load() < 0.5f && proximity > 0.05) // apply proximity to omni - { - float* writePointerOmni = omniEightBuffer.getWritePointer (0); - dsp::AudioBlock omniBlock(&writePointerOmni, 1, static_cast (numSamples)); - dsp::ProcessContextReplacing contextProxOmni(omniBlock); - proxCompIIR.process(contextProxOmni); - } - - if (doEq == 1 && zeroDelayMode->load() < 0.5f ) - { - // free field equalization - float* writePointerOmni = omniEightBuffer.getWritePointer (0); - dsp::AudioBlock ffEqOmniBlk(&writePointerOmni, 1, static_cast (numSamples)); - dsp::ProcessContextReplacing ffEqOmniCtx (ffEqOmniBlk); - ffEqOmniConv.process(ffEqOmniCtx); - - float* writePointerEight = omniEightBuffer.getWritePointer (1); - dsp::AudioBlock ffEqEightBlk(&writePointerEight, 1, static_cast (numSamples)); - dsp::ProcessContextReplacing ffEqEightCtx (ffEqEightBlk); - ffEqEightConv.process(ffEqEightCtx); - } - else if (doEq == 2 && zeroDelayMode->load() < 0.5f ) - { - // diffuse field equalization - float* writePointerOmni = omniEightBuffer.getWritePointer (0); - dsp::AudioBlock dfEqOmniBlk(&writePointerOmni, 1, static_cast (numSamples)); - dsp::ProcessContextReplacing dfEqOmniCtx (dfEqOmniBlk); - dfEqOmniConv.process(dfEqOmniCtx); - - float* writePointerEight = omniEightBuffer.getWritePointer (1); - dsp::AudioBlock dfEqEightBlk(&writePointerEight, 1, static_cast (numSamples)); - dsp::ProcessContextReplacing dfEqEightCtx (dfEqEightBlk); - dfEqEightConv.process(dfEqEightCtx); - } - - int nActiveBands = nBands; - - // 1-band EQ - if (zeroDelayMode->load() > 0.5f ) - nActiveBands = 1; - - for (int i = 0; i < nActiveBands; ++i) - { - // copy input buffer for each band - filterBankBuffer.copyFrom (2*i, 0, omniEightBuffer, 0, 0, numSamples); - filterBankBuffer.copyFrom (2*i+1, 0, omniEightBuffer, 1, 0, numSamples); - } - - // 5-band EQ - if (zeroDelayMode->load() < 0.5f && nActiveBands > 1) - { - if (!convolversReady) - { - return; - } - - // CODEREVIEW TODO: evaluate convolver use of FFT in JUCE::dsp to determine latency effects - Can we Replace with our own convolver code? - - for (int i = 0; i < nActiveBands; ++i) - { - // omni - float* writePointerOmni = filterBankBuffer.getWritePointer (2 * i); - dsp::AudioBlock subBlk (&writePointerOmni, 1, static_cast (numSamples)); - dsp::ProcessContextReplacing filterCtx (subBlk); - convolvers[2 * i].process (filterCtx); // mono processing - - // eight - float* writePointerEight = filterBankBuffer.getWritePointer (2 * i + 1); - dsp::AudioBlock subBlk2 (&writePointerEight, 1, static_cast (numSamples)); - dsp::ProcessContextReplacing filterCtx2 (subBlk2); - convolvers[2 * i + 1].process (filterCtx2); // mono processing - } - convolversReady = true; - } - - // !J! Deprecated: - // getPlayHead()->getCurrentPosition(info); - getPlayHead()->getPosition(); - termControlWaveform.pushBuffer(buffer); - - if (trackingActive) - trackSignalEnergy(); - - createPolarPatterns (buffer); -} - -void PolarDesignerAudioProcessor::processBlockBypassed (AudioBuffer& buffer, [[maybe_unused]]MidiBuffer& midiMessages) -{ - if (!isBypassed) { - isBypassed = true; - updateLatency(); - } - - jassert (getLatencySamples() == 0); - - for (int ch = getMainBusNumInputChannels(); ch < getTotalNumOutputChannels(); ++ch) - buffer.clear (ch, 0, buffer.getNumSamples()); -} + if (layouts.getMainOutputChannelSet().isDisabled()) + return false; -//============================================================================== -bool PolarDesignerAudioProcessor::hasEditor() const -{ - return true; // (change this to false if you choose to not supply an editor) -} + return true; + } -AudioProcessorEditor* PolarDesignerAudioProcessor::createEditor() -{ - return new PolarDesignerAudioProcessorEditor (*this, vtsParams); -} + void PolarDesignerAudioProcessor::processBlock (AudioBuffer& buffer, [[maybe_unused]] MidiBuffer& midiMessages) + { + ScopedNoDenormals noDenormals; + + if (isBypassed) + { + isBypassed = false; + updateLatency(); + } + + int numSamples = buffer.getNumSamples(); + + // create omni and eight signals + createOmniAndEightSignals (buffer); + + // proximity compensation filter + auto proximity = (proxOnOff->load() == 1.f) ? proxDistance->load() : 0.f; + if (zeroDelayMode->load() < 0.5f && proximity < -0.05) // reduce proximity effect only on figure-of-eight + { + float* writePointerEight = omniEightBuffer.getWritePointer (1); + dsp::AudioBlock eightBlock (&writePointerEight, 1, static_cast (numSamples)); + dsp::ProcessContextReplacing contextProxEight (eightBlock); + proxCompIIR.process (contextProxEight); + } + else if (zeroDelayMode->load() < 0.5f && proximity > 0.05) // apply proximity to omni + { + float* writePointerOmni = omniEightBuffer.getWritePointer (0); + dsp::AudioBlock omniBlock (&writePointerOmni, 1, static_cast (numSamples)); + dsp::ProcessContextReplacing contextProxOmni (omniBlock); + proxCompIIR.process (contextProxOmni); + } + + if (doEq == 1 && zeroDelayMode->load() < 0.5f) + { + // free field equalization + float* writePointerOmni = omniEightBuffer.getWritePointer (0); + dsp::AudioBlock ffEqOmniBlk (&writePointerOmni, 1, static_cast (numSamples)); + dsp::ProcessContextReplacing ffEqOmniCtx (ffEqOmniBlk); + ffEqOmniConv.process (ffEqOmniCtx); + + float* writePointerEight = omniEightBuffer.getWritePointer (1); + dsp::AudioBlock ffEqEightBlk (&writePointerEight, 1, static_cast (numSamples)); + dsp::ProcessContextReplacing ffEqEightCtx (ffEqEightBlk); + ffEqEightConv.process (ffEqEightCtx); + } + else if (doEq == 2 && zeroDelayMode->load() < 0.5f) + { + // diffuse field equalization + float* writePointerOmni = omniEightBuffer.getWritePointer (0); + dsp::AudioBlock dfEqOmniBlk (&writePointerOmni, 1, static_cast (numSamples)); + dsp::ProcessContextReplacing dfEqOmniCtx (dfEqOmniBlk); + dfEqOmniConv.process (dfEqOmniCtx); + + float* writePointerEight = omniEightBuffer.getWritePointer (1); + dsp::AudioBlock dfEqEightBlk (&writePointerEight, 1, static_cast (numSamples)); + dsp::ProcessContextReplacing dfEqEightCtx (dfEqEightBlk); + dfEqEightConv.process (dfEqEightCtx); + } + + int nActiveBands = nBands; + + // 1-band EQ + if (zeroDelayMode->load() > 0.5f) + nActiveBands = 1; + + for (int i = 0; i < nActiveBands; ++i) + { + // copy input buffer for each band + filterBankBuffer.copyFrom (2 * i, 0, omniEightBuffer, 0, 0, numSamples); + filterBankBuffer.copyFrom (2 * i + 1, 0, omniEightBuffer, 1, 0, numSamples); + } + + // 5-band EQ + if (zeroDelayMode->load() < 0.5f && nActiveBands > 1) + { + if (!convolversReady) + { + return; + } + + // CODEREVIEW TODO: evaluate convolver use of FFT in JUCE::dsp to determine latency effects - Can we Replace with our own convolver code? + + for (int i = 0; i < nActiveBands; ++i) + { + // omni + float* writePointerOmni = filterBankBuffer.getWritePointer (2 * i); + dsp::AudioBlock subBlk (&writePointerOmni, 1, static_cast (numSamples)); + dsp::ProcessContextReplacing filterCtx (subBlk); + convolvers[2 * i].process (filterCtx); // mono processing + + // eight + float* writePointerEight = filterBankBuffer.getWritePointer (2 * i + 1); + dsp::AudioBlock subBlk2 (&writePointerEight, 1, static_cast (numSamples)); + dsp::ProcessContextReplacing filterCtx2 (subBlk2); + convolvers[2 * i + 1].process (filterCtx2); // mono processing + } + convolversReady = true; + } + + // !J! Deprecated: + // getPlayHead()->getCurrentPosition(info); + getPlayHead()->getPosition(); + termControlWaveform.pushBuffer (buffer); + + if (trackingActive) + trackSignalEnergy(); + + createPolarPatterns (buffer); + } -//============================================================================== + void PolarDesignerAudioProcessor::processBlockBypassed (AudioBuffer& buffer, [[maybe_unused]] MidiBuffer& midiMessages) + { + if (!isBypassed) + { + isBypassed = true; + updateLatency(); + } -void PolarDesignerAudioProcessor::getStateInformation (MemoryBlock& destData) -{ - // You should use this method to store your parameters in the memory block. - // You could do that either as raw data, or use the XML or ValueTree classes - // as intermediaries to make it easy to save and load complex data. - vtsParams.state.setProperty("ffDfEq", var(doEq), nullptr); - vtsParams.state.setProperty("oldProxDistance", var(oldProxDistance), nullptr); - - if (abLayerState == 1) - { - layerA = vtsParams.copyState(); - doEqA = doEq; - if (proxDistance->load() != 0.0f) { oldProxDistanceA = proxDistance->load(); } - } - if (abLayerState == 0) - { - layerB = vtsParams.copyState(); - doEqB = doEq; - if (proxDistance->load() != 0.0f) { oldProxDistanceB = proxDistance->load(); } - } - - layerA.setProperty("ffDfEq", var(doEqA), nullptr); - layerA.setProperty("oldProxDistance", var(oldProxDistanceA), nullptr); - layerB.setProperty("ffDfEq", var(doEqB), nullptr); - layerB.setProperty("oldProxDistance", var(oldProxDistanceB), nullptr); - - ValueTree vtsState = vtsParams.copyState(); - ValueTree AState = layerA.createCopy(); - ValueTree BState = layerB.createCopy(); - - saveStates.removeAllChildren(nullptr); - saveStates.addChild(vtsState, 0, nullptr); - saveStates.addChild(AState, 1, nullptr); - saveStates.addChild(BState, 2, nullptr); - - std::unique_ptr xml (saveStates.createXml()); - copyXmlToBinary (*xml, destData); -} - -void PolarDesignerAudioProcessor::setStateInformation (const void* data, int sizeInBytes) -{ - // You should use this method to restore your parameters from this memory block, - // whose contents will have been created by the getStateInformation() call. - std::unique_ptr xmlState (getXmlFromBinary (data, sizeInBytes)); - if (xmlState != nullptr) - { - if (xmlState->hasTagName (saveStates.getType())) - { - saveStates = ValueTree::fromXml (*xmlState); - vtsParams.replaceState(saveStates.getChild(1)); - } - else if (xmlState->hasTagName (vtsParams.state.getType())) - { - vtsParams.state = ValueTree::fromXml (*xmlState); - } - } - - layerB = saveStates.getChild(2).createCopy(); - - if (vtsParams.state.hasProperty("ffDfEq")) - { - Value val = vtsParams.state.getPropertyAsValue("ffDfEq", nullptr); - if (val.getValue().toString() != "") - { - doEq = static_cast(val.getValue()); - } - } - if (vtsParams.state.hasProperty("oldProxDistance")) - { - Value val = vtsParams.state.getPropertyAsValue("oldProxDistance", nullptr); - if (val.getValue().toString() != "") - { - oldProxDistance = static_cast(val.getValue()); - } - } - - if (layerB.hasProperty("ffDfEq")) - { - Value val = layerB.getPropertyAsValue("ffDfEq", nullptr); - if (val.getValue().toString() != "") - { - doEqB = static_cast(val.getValue()); - } - } - if (layerB.hasProperty("oldProxDistance")) - { - Value val = layerB.getPropertyAsValue("oldProxDistance", nullptr); - if (val.getValue().toString() != "") - { - oldProxDistanceB = static_cast(val.getValue()); - } - } - nBands = static_cast(nBandsPtr->load()) + 1; - activeBandsChanged = true; - zeroDelayModeChanged = true; - ffDfEqChanged = true; - computeAllFilterCoefficients(); - initAllConvolvers(); - repaintDEQ = true; -} - -void PolarDesignerAudioProcessor::parameterChanged (const String ¶meterID, float newValue) -{ - if (parameterID.startsWith("xOverF") && !loadingFile) - { - int idx = parameterID.getTrailingIntValue() - 1; - computeFilterCoefficients(idx); - initConvolver(idx); - repaintDEQ = true; - } - else if (parameterID.startsWith("solo")) - { - soloActive = false; - for (int i = 0; iload() >= 0.5) - soloActive = true; - } - } - else if (parameterID.startsWith("alpha")) - { - repaintDEQ = true; - } - else if (parameterID == "nrBands") - { - nBands = static_cast (nBandsPtr->load()) + 1; - resetXoverFreqs(); - activeBandsChanged = true; - computeAllFilterCoefficients(); - initAllConvolvers(); - } - else if (parameterID == "proximity") - { - setProxCompCoefficients(proxDistance->load()); - } - else if (parameterID == "zeroDelayMode") - { - updateLatency(); - - if (newValue == 0.0f) - { - if (abLayerState == 0) - { - vtsParams.getParameter ("proximity")->setValueNotifyingHost (vtsParams.getParameter("proximity")->convertTo0to1(oldProxDistanceB)); - } - else - { - vtsParams.getParameter ("proximity")->setValueNotifyingHost (vtsParams.getParameter("proximity")->convertTo0to1(oldProxDistanceA)); - } - zeroDelayModeChanged = true; - computeAllFilterCoefficients(); - initAllConvolvers(); - } - else - { - if (abLayerState == 0 && !abLayerChanged.get()) - { - oldProxDistanceB = proxDistance->load(); - } - else if (abLayerState == 1 && !abLayerChanged.get()) - { - oldProxDistanceA = proxDistance->load(); - } - vtsParams.getParameter ("proximity")->setValueNotifyingHost (vtsParams.getParameter("proximity")->convertTo0to1(0)); - zeroDelayModeChanged = true; - } - } - else if (parameterID == "syncChannel" && syncChannelPtr->load() >= 0.5f) - { - int ch = (int) syncChannelPtr->load() - 1; - ParamsToSync& paramsToSync = sharedParams.get().syncParams.getReference(ch); - - if (!paramsToSync.paramsValid) // init all params - { - for (int i = 0; i < 5; ++i) - { - paramsToSync.solo[i] = static_cast (soloBand[i]->load()); - paramsToSync.mute[i] = static_cast (muteBand[i]->load()); - paramsToSync.dirFactors[i] = dirFactors[i]->load(); - paramsToSync.gains[i] = bandGains[i]->load(); - - if (i < 4) - paramsToSync.xOverFreqs[i] = xOverFreqs[i]->load(); - } - - paramsToSync.nrActiveBands = static_cast (nBandsPtr->load()); - paramsToSync.proximity = proxDistance->load(); - paramsToSync.proximityOnOff = static_cast (proxOnOff->load()); - - paramsToSync.allowBackwardsPattern = static_cast (allowBackwardsPattern->load()); - - if(!readingSharedParams) - { - paramsToSync.zeroDelayMode = static_cast (zeroDelayMode->load()); - paramsToSync.ffDfEq = doEq; - } - } - } - - // if parameters are synced -> set sharedParams - if (syncChannelPtr->load() >= 0.5f && !readingSharedParams) - { - int ch = (int) syncChannelPtr->load() - 1; - ParamsToSync& paramsToSync = sharedParams.get().syncParams.getReference(ch); - - if (parameterID.startsWith("xOverF") && !loadingFile) - { - int idx = parameterID.getTrailingIntValue() - 1; - paramsToSync.xOverFreqs[idx] = xOverFreqs[idx]->load(); - } - else if (parameterID.startsWith("solo")) - { - int idx = parameterID.getTrailingIntValue() - 1; - paramsToSync.solo[idx] = static_cast (soloBand[idx]->load()); - } - else if (parameterID.startsWith("mute")) - { - int idx = parameterID.getTrailingIntValue() - 1; - paramsToSync.mute[idx] = static_cast (muteBand[idx]->load()); - } - else if (parameterID.startsWith("alpha")) - { - int idx = parameterID.getTrailingIntValue() - 1; - paramsToSync.dirFactors[idx] = dirFactors[idx]->load(); - } - else if (parameterID == "nrBands") - { - paramsToSync.nrActiveBands = static_cast (nBandsPtr->load()); - } - else if (parameterID == "proximity") - { - paramsToSync.proximity = proxDistance->load(); - } - else if (parameterID == "proximityOnOff") - { - paramsToSync.proximityOnOff = static_cast (proxOnOff->load()); - } - else if (parameterID == "zeroDelayMode") - { - paramsToSync.zeroDelayMode = static_cast (zeroDelayMode->load()); - } - else if (parameterID.startsWith("gain")) - { - int idx = parameterID.getTrailingIntValue() - 1; - paramsToSync.gains[idx] = bandGains[idx]->load(); - } - else if (parameterID == "allowBackwardsPattern") - { - paramsToSync.allowBackwardsPattern = static_cast (allowBackwardsPattern->load()); - } - - } -} - -void PolarDesignerAudioProcessor::setEqState(int idx) -{ - doEq = idx; - - if (syncChannelPtr->load() >= 0.5f && !readingSharedParams) - { - int ch = (int) syncChannelPtr->load() - 1; - ParamsToSync& paramsToSync = sharedParams.get().syncParams.getReference(ch); - paramsToSync.ffDfEq = doEq; - } -} - -void PolarDesignerAudioProcessor::setAbLayer(bool state) -{ - abLayerState = state; - changeAbLayerState(); -} - -void PolarDesignerAudioProcessor::resetXoverFreqs() -{ - switch (nBands) { - case 1: - break; - - case 2: - for (int i = 0; i < nBands - 1; ++i) - { - vtsParams.getParameter ("xOverF" + String(i+1))->setValueNotifyingHost (hzToZeroToOne(i, INIT_XOVER_FREQS_2B[i])); - } - break; - - case 3: - for (int i = 0; i < nBands - 1; ++i) - { - vtsParams.getParameter ("xOverF" + String(i+1))->setValueNotifyingHost (hzToZeroToOne(i, INIT_XOVER_FREQS_3B[i])); - } - break; - - case 4: - for (int i = 0; i < nBands - 1; ++i) - { - vtsParams.getParameter ("xOverF" + String(i+1))->setValueNotifyingHost (hzToZeroToOne(i, INIT_XOVER_FREQS_4B[i])); - } - break; - - case 5: - for (int i = 0; i < nBands - 1; ++i) - { - vtsParams.getParameter ("xOverF" + String(i+1))->setValueNotifyingHost (hzToZeroToOne(i, INIT_XOVER_FREQS_5B[i])); - } - break; - - default: - jassert(false); - break; - } -} - -// compute filter bank filter coeffs and store in firFilterBuffer -void PolarDesignerAudioProcessor::computeAllFilterCoefficients() -{ - for (int i = 0; i < 4; ++i) - { - computeFilterCoefficients(i); - } -} - -void PolarDesignerAudioProcessor::computeFilterCoefficients(int crossoverNr) -{ - // only one band: no filtering - if (nBands == 1) - return; - - // lowest band is simple lowpass - if (crossoverNr == 0) - { - dsp::FilterDesign::FIRCoefficientsPtr lowpass = dsp::FilterDesign::designFIRLowpassWindowMethod(hzFromZeroToOne(0, xOverFreqs[0]->load()), currentSampleRate, static_cast (firLen - 1.0f), dsp::WindowingFunction::WindowingMethod::hamming); - float* lpCoeffs = lowpass->getRawCoefficients(); - firFilterBuffer.copyFrom(0, 0, lpCoeffs, firLen); - } - - // all the other bands are bandpass filters - for (int i = std::max(1, crossoverNr); i < std::min(crossoverNr + 2, nBands - 1); ++i) - { - float halfBandwidth = (hzFromZeroToOne(i, xOverFreqs[i]->load()) - hzFromZeroToOne(i-1, xOverFreqs[i-1]->load())) / 2; - dsp::FilterDesign::FIRCoefficientsPtr lp2bp = dsp::FilterDesign::designFIRLowpassWindowMethod(halfBandwidth, currentSampleRate, static_cast (firLen - 1.0f), dsp::WindowingFunction::WindowingMethod::hamming); - float* lp2bpCoeffs = lp2bp->getRawCoefficients(); - auto* filterBufferPointer = firFilterBuffer.getWritePointer(i); - for (int j=0; jload()); - // write bandpass transformed fir coeffs to buffer - *(filterBufferPointer+j) = 2 * *(lp2bpCoeffs+j) * std::cosf(static_cast (MathConstants::twoPi * fCenter / currentSampleRate * (j - (firLen - 1.0f) / 2.0f))); - } - } - - if (crossoverNr == nBands - 2) - { - // highest band is highpass (via frequency transform) - float hpBandwidth = static_cast (currentSampleRate / 2 - hzFromZeroToOne (nBands - 2, xOverFreqs[nBands - 2]->load())); - auto* filterBufferPointer = firFilterBuffer.getWritePointer(nBands-1); - dsp::FilterDesign::FIRCoefficientsPtr lp2hp = dsp::FilterDesign::designFIRLowpassWindowMethod(hpBandwidth, currentSampleRate, static_cast(firLen - 1.0f), dsp::WindowingFunction::WindowingMethod::hamming); - float* lp2hpCoeffs = lp2hp->getRawCoefficients(); - for (int i=0; i::pi * (i - (firLen - 1.0f) / 2.0f)); - } - } - -} - -void PolarDesignerAudioProcessor::initAllConvolvers() -{ - convolversReady = false; - - // build filters and fill firFilterBuffer - dsp::AudioBlock convBlk (firFilterBuffer); - dsp::ProcessSpec convSpec {currentSampleRate, static_cast(currentBlockSize), 1}; - for (int i = 0; i < nBands; ++i) // prepare nBands mono convolvers - { - AudioBuffer convSingleBuffOmni(1, firLen); - convSingleBuffOmni.copyFrom(0, 0, firFilterBuffer, i, 0, firLen); - - AudioBuffer convSingleBuffEight(1, firLen); - convSingleBuffEight.copyFrom(0, 0, firFilterBuffer, i, 0, firLen); - - // omni convolver - convolvers[2 * i].prepare (convSpec); // must be called before loading IR - convolvers[2 * i].loadImpulseResponse(std::move(convSingleBuffOmni), currentSampleRate, Convolution::Stereo::no, Convolution::Trim::no, Convolution::Normalise::no); - - // eight convolver - convolvers[2 * i + 1].prepare (convSpec); // must be called before loading IR - convolvers[2 * i + 1].loadImpulseResponse(std::move(convSingleBuffEight), currentSampleRate, Convolution::Stereo::no, Convolution::Trim::no, Convolution::Normalise::no); - } - convolversReady = true; -} - -void PolarDesignerAudioProcessor::initConvolver(int convNr) -{ - convolversReady = false; - - // build filters and fill firFilterBuffer - dsp::AudioBlock convBlk (firFilterBuffer); - dsp::ProcessSpec convSpec {currentSampleRate, static_cast(currentBlockSize), 1}; - - // update two convolvers: if one crossover frequency changes, two neighbouring bands need new filters - for (int i = convNr; i < convNr + 2; ++i) - { - AudioBuffer convSingleBuffOmni(1, firLen); - convSingleBuffOmni.copyFrom(0, 0, firFilterBuffer, i, 0, firLen); - - AudioBuffer convSingleBuffEight(1, firLen); - convSingleBuffEight.copyFrom(0, 0, firFilterBuffer, i, 0, firLen); - - // omni convolver - convolvers[2 * i].prepare (convSpec); // must be called before loading IR - convolvers[2 * i].loadImpulseResponse(std::move(convSingleBuffOmni), currentSampleRate, Convolution::Stereo::no, Convolution::Trim::no, Convolution::Normalise::no); - - // eight convolver - convolvers[2 * i + 1].prepare (convSpec); // must be called before loading IR - convolvers[2 * i + 1].loadImpulseResponse(std::move(convSingleBuffEight), currentSampleRate, Convolution::Stereo::no, Convolution::Trim::no, Convolution::Normalise::no); - } - convolversReady = true; -} - -void PolarDesignerAudioProcessor::createOmniAndEightSignals (AudioBuffer& buffer) -{ - int numSamples = buffer.getNumSamples(); - // calculate omni part - const float* readPointerFront = buffer.getReadPointer (0); - const float* readPointerBack = buffer.getReadPointer (1); - float* writePointerOmni = omniEightBuffer.getWritePointer (0); - FloatVectorOperations::copy (writePointerOmni, readPointerFront, numSamples); - FloatVectorOperations::add (writePointerOmni, readPointerBack, numSamples); - - // calculate fig-of-eight part - float* writePointerEight = omniEightBuffer.getWritePointer (1); - FloatVectorOperations::copy (writePointerEight, readPointerFront, numSamples); - FloatVectorOperations::subtract (writePointerEight, readPointerBack, numSamples); -} - -void PolarDesignerAudioProcessor::createPolarPatterns(AudioBuffer& buffer) -{ - int numSamples = buffer.getNumSamples(); - buffer.clear(); - - int nActiveBands = nBands; - if (zeroDelayMode->load() > 0.5f) - nActiveBands = 1; - - for (int i = 0; i < nActiveBands; ++i) - { - if ((muteBand[i]->load() > 0.5 && soloBand[i]->load() < 0.5) || (soloActive && soloBand[i]->load() < 0.5)) - continue; - - // calculate patterns and add to output buffer - const float* readPointerOmni = filterBankBuffer.getReadPointer (2 * i); - const float* readPointerEight = filterBankBuffer.getReadPointer (2 * i + 1); - - float oldGain = Decibels::decibelsToGain(oldBandGains[i], -59.91f); - float gain = Decibels::decibelsToGain(bandGains[i]->load(), -59.91f); - - // add with ramp to prevent crackling noises - buffer.addFromWithRamp(0, 0, readPointerOmni, numSamples, - (1 - std::abs (oldDirFactors[i])) * oldGain, - (1 - std::abs (dirFactors[i]->load())) * gain); - buffer.addFromWithRamp(0, 0, readPointerEight, numSamples, - oldDirFactors[i] * oldGain, - dirFactors[i]->load() * gain); - - oldDirFactors[i] = dirFactors[i]->load(); - oldBandGains[i] = bandGains[i]->load(); - } - - // delay needs to be running constantly to prevent clicks - delayBuffer.copyFrom(0, 0, buffer, 0, 0, numSamples); - dsp::AudioBlock delayBlock(delayBuffer); - dsp::ProcessContextReplacing delayContext(delayBlock); - delay.process(delayContext); - - if (nActiveBands == 1 && zeroDelayMode->load() < 0.5f) { - buffer.copyFrom(0, 0, delayBuffer, 0, 0, numSamples); - } - - // copy to second output channel -> this generates loud glitches in pro tools if mono output configuration is used - // -> check getMainBusNumOutputChannels() - if (buffer.getNumChannels() == 2 && getMainBusNumOutputChannels() == 2) - buffer.copyFrom(1, 0, buffer, 0, 0, numSamples); -} - -void PolarDesignerAudioProcessor::setLastDir(File newLastDir) -{ - lastDir = newLastDir; - const var v (lastDir.getFullPathName()); - properties->setValue ("presetFolder", v); -} - -Result PolarDesignerAudioProcessor::loadPreset(const File& presetFile) -{ - var parsedJson; - if (!presetFile.exists()) - return Result::fail ("File does not exist!"); - - String jsonString = presetFile.loadFileAsString(); - Result result = JSON::parse (jsonString, parsedJson); - if (!result.wasOk()) - return Result::fail ("File could not be parsed: Please provide valid JSON!"); - - for (auto &it : presetProperties) - { - if (!parsedJson.hasProperty (it)) - return Result::fail ("Corrupt preset file: No '" + it + "' property found."); - } - - loadingFile = true; - - float x = parsedJson.getProperty ("nrActiveBands", parsedJson); - vtsParams.getParameter ("nrBands")->setValueNotifyingHost (vtsParams.getParameter ("nrBands")->convertTo0to1(x - 1)); - - for (int i = 0; i < 4; ++i) - { - x = parsedJson.getProperty ("xOverF" + String(i+1), parsedJson); - vtsParams.getParameter ("xOverF" + String(i+1))->setValueNotifyingHost (hzToZeroToOne(i, x)); - } - - NormalisableRange dfRange = vtsParams.getParameter("alpha1")->getNormalisableRange(); - - for (int i = 0; i < 5; ++i) - { - x = parsedJson.getProperty ("dirFactor" + String(i+1), parsedJson); - if (x < dfRange.start || x > dfRange.end) - return Result::fail ("DirFactor" + String(i+1) + " needs to be between " + String(dfRange.start) + " and " + String(dfRange.end) + "."); - vtsParams.getParameter ("alpha" + String(i+1))->setValueNotifyingHost (dfRange.convertTo0to1(x)); - - x = parsedJson.getProperty ("gain" + String(i+1), parsedJson); - vtsParams.getParameter ("gain" + String(i+1))->setValueNotifyingHost (vtsParams.getParameter("gain1")->convertTo0to1(x)); - - x = parsedJson.getProperty ("solo" + String(i+1), parsedJson); - vtsParams.getParameter ("solo" + String(i+1))->setValueNotifyingHost (vtsParams.getParameter("solo1")->convertTo0to1(x)); - - x = parsedJson.getProperty ("mute" + String(i+1), parsedJson); - vtsParams.getParameter ("mute" + String(i+1))->setValueNotifyingHost (vtsParams.getParameter("solo1")->convertTo0to1(x)); - } - - doEq = parsedJson.getProperty ("ffDfEq", parsedJson); - - x = parsedJson.getProperty ("proximity", parsedJson); - vtsParams.getParameter ("proximity")->setValueNotifyingHost (vtsParams.getParameter("proximity")->convertTo0to1(x)); - - x = parsedJson.getProperty("proximityOnOff", parsedJson); - vtsParams.getParameter("proximityOnOff")->setValueNotifyingHost(vtsParams.getParameter("proximityOnOff")->convertTo0to1(x)); - - loadingFile = false; - - // set parameters - nBands = static_cast(nBandsPtr->load()) + 1; - activeBandsChanged = true; - computeAllFilterCoefficients(); - initAllConvolvers(); - repaintDEQ = true; - - return Result::ok(); -} - -Result PolarDesignerAudioProcessor::savePreset (File destination) -{ - DynamicObject* jsonObj = new DynamicObject(); - char versionString[10]; - strcpy(versionString, "v"); - strcat(versionString, JucePlugin_VersionString); - jsonObj->setProperty("Description", var("This preset file was created with the Austrian Audio PolarDesigner plugin " - + String(versionString) + ", for more information see www.austrian.audio .")); - jsonObj->setProperty ("nrActiveBands", nBands); - jsonObj->setProperty ("xOverF1", static_cast(hzFromZeroToOne(0, xOverFreqs[0]->load()))); - jsonObj->setProperty ("xOverF2", static_cast(hzFromZeroToOne(1, xOverFreqs[1]->load()))); - jsonObj->setProperty ("xOverF3", static_cast(hzFromZeroToOne(2, xOverFreqs[2]->load()))); - jsonObj->setProperty ("xOverF4", static_cast(hzFromZeroToOne(3, xOverFreqs[3]->load()))); - jsonObj->setProperty ("dirFactor1", dirFactors[0]->load()); - jsonObj->setProperty ("dirFactor2", dirFactors[1]->load()); - jsonObj->setProperty ("dirFactor3", dirFactors[2]->load()); - jsonObj->setProperty ("dirFactor4", dirFactors[3]->load()); - jsonObj->setProperty ("dirFactor5", dirFactors[4]->load()); - jsonObj->setProperty ("gain1", bandGains[0]->load()); - jsonObj->setProperty ("gain2", bandGains[1]->load()); - jsonObj->setProperty ("gain3", bandGains[2]->load()); - jsonObj->setProperty ("gain4", bandGains[3]->load()); - jsonObj->setProperty ("gain5", bandGains[4]->load()); - jsonObj->setProperty ("solo1", soloBand[0]->load()); - jsonObj->setProperty ("solo2", soloBand[1]->load()); - jsonObj->setProperty ("solo3", soloBand[2]->load()); - jsonObj->setProperty ("solo4", soloBand[3]->load()); - jsonObj->setProperty ("solo5", soloBand[4]->load()); - jsonObj->setProperty ("mute1", muteBand[0]->load()); - jsonObj->setProperty ("mute2", muteBand[1]->load()); - jsonObj->setProperty ("mute3", muteBand[2]->load()); - jsonObj->setProperty ("mute4", muteBand[3]->load()); - jsonObj->setProperty ("mute5", muteBand[4]->load()); - jsonObj->setProperty ("ffDfEq", doEq); - jsonObj->setProperty ("proximity", proxDistance->load()); - jsonObj->setProperty("proximityOnOff", proxOnOff->load()); - - String jsonString = JSON::toString (var (jsonObj), false, 2); - if (destination.replaceWithText (jsonString)) - return Result::ok(); - else - return Result::fail ("Could not write preset file. Check file access permissions."); -} - -float PolarDesignerAudioProcessor::hzToZeroToOne(int idx, float hz) -{ - switch (nBands) { - case 1: - return 0; - break; - - case 2: - return (hz - XOVER_RANGE_START_2B[idx]) / (XOVER_RANGE_END_2B[idx] - XOVER_RANGE_START_2B[idx]); - break; - - case 3: - return (hz - XOVER_RANGE_START_3B[idx]) / (XOVER_RANGE_END_3B[idx] - XOVER_RANGE_START_3B[idx]); - break; - - case 4: - return (hz - XOVER_RANGE_START_4B[idx]) / (XOVER_RANGE_END_4B[idx] - XOVER_RANGE_START_4B[idx]); - break; - - case 5: - return (hz - XOVER_RANGE_START_5B[idx]) / (XOVER_RANGE_END_5B[idx] - XOVER_RANGE_START_5B[idx]); - break; - - default: - jassert(false); - break; - } - return 0; -} - -float PolarDesignerAudioProcessor::hzFromZeroToOne(int idx, float val) -{ - switch (nBands) { - case 1: - return 0; - break; - - case 2: - return XOVER_RANGE_START_2B[idx] + val * (XOVER_RANGE_END_2B[idx] - XOVER_RANGE_START_2B[idx]); - break; - - case 3: - return XOVER_RANGE_START_3B[idx] + val * (XOVER_RANGE_END_3B[idx] - XOVER_RANGE_START_3B[idx]); - break; - - case 4: - return XOVER_RANGE_START_4B[idx] + val * (XOVER_RANGE_END_4B[idx] - XOVER_RANGE_START_4B[idx]); - break; - - case 5: - return XOVER_RANGE_START_5B[idx] + val * (XOVER_RANGE_END_5B[idx] - XOVER_RANGE_START_5B[idx]); - break; - - default: - jassert(false); - break; - } - return 0; -} - -float PolarDesignerAudioProcessor::getXoverSliderRangeStart (int sliderNum) -{ - switch (nBands) { - case 2: - return XOVER_RANGE_START_2B[sliderNum]; - break; - - case 3: - return XOVER_RANGE_START_3B[sliderNum]; - break; - - case 4: - return XOVER_RANGE_START_4B[sliderNum]; - break; - - case 5: - return XOVER_RANGE_START_5B[sliderNum]; - break; - - default: - jassert(false); - break; - } - return 0; -} - -float PolarDesignerAudioProcessor::getXoverSliderRangeEnd (int sliderNum) -{ - switch (nBands) { - case 2: - return XOVER_RANGE_END_2B[sliderNum]; - break; - - case 3: - return XOVER_RANGE_END_3B[sliderNum]; - break; - - case 4: - return XOVER_RANGE_END_4B[sliderNum]; - break; - - case 5: - return XOVER_RANGE_END_5B[sliderNum]; - break; - - default: - jassert(false); - break; - } - return 0; -} - -void PolarDesignerAudioProcessor::startTracking(bool trackDisturber) -{ - if (trackDisturber) - { - trackingDisturber = true; - for (int i = 0; i < 5; ++i) - { - omniSqSumDist[i] = 0.0f; - eightSqSumDist[i] = 0.0f; - omniEightSumDist[i] = 0.0f; - } - } - else - { - trackingDisturber = false; - for (int i = 0; i < 5; ++i) - { - omniSqSumSig[i] = 0.0f; - eightSqSumSig[i] = 0.0f; - omniEightSumSig[i] = 0.0f; - } - } - - nrBlocksRecorded = 0; - trackingActive = true; -} - -void PolarDesignerAudioProcessor::stopTracking(int applyOptimalPattern) -{ - trackingActive = false; - if (applyOptimalPattern == 1) - { - if (trackingDisturber) - { - if (nrBlocksRecorded != 0) - { - for (int i = 0; i < 5; ++i) - { - omniSqSumDist[i] = omniSqSumDist[i]/nrBlocksRecorded; - eightSqSumDist[i] = eightSqSumDist[i]/nrBlocksRecorded; - omniEightSumDist[i] = omniEightSumDist[i]/nrBlocksRecorded; - } - } - setMinimumDisturbancePattern(); - } - else - { - if (nrBlocksRecorded != 0) - { - for (int i = 0; i < 5; ++i) - { - omniSqSumSig[i] = omniSqSumSig[i]/nrBlocksRecorded; - eightSqSumSig[i] = eightSqSumSig[i]/nrBlocksRecorded; - omniEightSumSig[i] = omniEightSumSig[i]/nrBlocksRecorded; - } - } - setMaximumSignalPattern(); - } - } - else if (applyOptimalPattern == 2) // max sig-to-dist - { - if (trackingDisturber) - { - if (nrBlocksRecorded != 0) - { - for (int i = 0; i < 5; ++i) - { - omniSqSumDist[i] = omniSqSumDist[i]/nrBlocksRecorded; - eightSqSumDist[i] = eightSqSumDist[i]/nrBlocksRecorded; - omniEightSumDist[i] = omniEightSumDist[i]/nrBlocksRecorded; - } - } - disturberRecorded = true; - } - else - { - if (nrBlocksRecorded != 0) - { - for (int i = 0; i < 5; ++i) - { - omniSqSumSig[i] = omniSqSumSig[i]/nrBlocksRecorded; - eightSqSumSig[i] = eightSqSumSig[i]/nrBlocksRecorded; - omniEightSumSig[i] = omniEightSumSig[i]/nrBlocksRecorded; - } - } - signalRecorded = true; - } - maximizeSigToDistRatio(); - } -} - -void PolarDesignerAudioProcessor::trackSignalEnergy() -{ - int numSamples = filterBankBuffer.getNumSamples(); - for (int i = 0; i < nBands; ++i) - { - const float* readPointerOmni = filterBankBuffer.getReadPointer (2*i); - const float* readPointerEight = filterBankBuffer.getReadPointer (2*i+1); - if (trackingDisturber) - { - for (int j = 0; j < numSamples; ++j) - { - float omniSample = readPointerOmni[j]; - omniSqSumDist[i] += omniSample * omniSample / numSamples; - float eightSample = readPointerEight[j]; - eightSqSumDist[i] += eightSample * eightSample / numSamples; - omniEightSumDist[i] += omniSample * eightSample / numSamples; - } - } - else - { - for (int j = 0; j < numSamples; ++j) - { - float omniSample = readPointerOmni[j]; - omniSqSumSig[i] += omniSample * omniSample / numSamples; - float eightSample = readPointerEight[j]; - eightSqSumSig[i] += eightSample * eightSample / numSamples; - omniEightSumSig[i] += omniSample * eightSample / numSamples; - } - } - } - ++nrBlocksRecorded; -} - - - -void PolarDesignerAudioProcessor::setMinimumDisturbancePattern() -{ - float disturberPower = 0.0f; - float minPowerAlpha = 0.0f; - float alphaStart = 0.0f; - if (allowBackwardsPattern->load() == 1.0f) - alphaStart = -0.5f; - - for (int i = 0; isetValueNotifyingHost (vtsParams.getParameter("alpha1")->convertTo0to1 (minPowerAlpha)); - disturberRecorded = true; - } - } -} - -void PolarDesignerAudioProcessor::setMaximumSignalPattern() -{ - float signalPower = 0.0f; - float maxPowerAlpha = 0.0f; - float alphaStart = 0.0f; - if (allowBackwardsPattern->load() == 1.0f) - alphaStart = -0.5f; - - for (int i = 0; i < nBands; ++i) - { - for (float alpha = alphaStart; alpha <= 1.0f; alpha += 0.01f) - { - float currentPower = std::pow((1.0f-std::abs(alpha)), 2.0f) * omniSqSumSig[i] + std::pow(alpha, 2.0f) * eightSqSumSig[i] + 2.0f * (1.0f-std::abs(alpha)) * alpha * omniEightSumSig[i]; - if ((floatsEquivalent(alpha, alphaStart)) || currentPower > signalPower) - { - signalPower = currentPower; - maxPowerAlpha = alpha; - } - } - if (signalPower != 0.0f) - { - vtsParams.getParameter ("alpha" + String(i+1))->setValueNotifyingHost (vtsParams.getParameter("alpha1")->convertTo0to1 (maxPowerAlpha)); - signalRecorded = true; - } - } -} - -void PolarDesignerAudioProcessor::maximizeSigToDistRatio() -{ - float distToSigRatio = 0.0f; - float maxDistToSigAlpha = 0.0f; - float alphaStart = 0.0f; - if (allowBackwardsPattern->load() == 1.0f) - alphaStart = -0.5f; - - for (int i = 0; i distToSigRatio) - { - distToSigRatio = currentRatio; - maxDistToSigAlpha = alpha; - } - } - if (distToSigRatio != 0.0f) - vtsParams.getParameter ("alpha" + String(i+1))->setValueNotifyingHost (vtsParams.getParameter("alpha1")->convertTo0to1 (maxDistToSigAlpha)); - } -} - -void PolarDesignerAudioProcessor::setProxCompCoefficients(float distance) -{ - // The speed of sound at standard air pressure/20C - meters per second - const float c = 343.0f; - float fs = (float)getSampleRate(); - - // float b0 = -c / (fs * 4 * distance) + 1; - // float b1 = -exp(-c / (fs * 2 * distance)) * (1 + c / (fs * 4 * distance)); - // float a0 = 1; - // float a1 = -exp(-c / (fs * 2 * distance)); - - // use logarithmic fader impact: equation is for fader between -1.0 .. 1.0 - // returns values between 1 .. 0.1 - float a = (0.05f - 1.0f) / (-log(1.1f) + log(0.1f)); - float b = 1.0f + a * log(0.1f); - float r = -a * log(std::abs(distance) + 0.1f) + b; - - float b0, b1, a0, a1; - - // normalized to r_ref = 1m - if (distance <= 0) //bass cut - { - if (r < 0.01f) - r = 0.01f; - - b0 = c * (r - 1.0f) / (fs * 2.0f * r) + 1.0f; - b1 = -exp(-c / (fs * r)) * (1.0f - c * (r - 1.0f) / (fs * 2.0f * r)); - a0 = 1.0f; - a1 = -exp(-c / (fs * r)); - } - else // bass boost, careful: instable for r<0.05 - { - if (r < 0.05f) - r = 0.05f; - - b0 = c * (1.0f - r) / (fs * 2.0f * r) + 1.0f; - b1 = -exp(-c / fs) * (1.0f - c * (1.0f - r) / (fs * 2.0f * r)); - a0 = 1.0f; - a1 = -exp(-c / fs); - } - - *proxCompIIR.coefficients = dsp::IIR::Coefficients(b0,b1,a0,a1); -} - -void PolarDesignerAudioProcessor::timerCallback() -{ - if (syncChannelPtr->load() > 0.5f) - { - readingSharedParams = true; - - int ch = (int) syncChannelPtr->load() - 1; - ParamsToSync& paramsToSync = sharedParams.get().syncParams.getReference(ch); - - if ((int)nBandsPtr->load() != paramsToSync.nrActiveBands) - vtsParams.getParameter ("nrBands")->setValueNotifyingHost (vtsParams.getParameterRange ("nrBands").convertTo0to1 (paramsToSync.nrActiveBands)); - - for (int i = 0; i < 5; ++i) - { - if (!floatsEquivalent(dirFactors[i]->load(), paramsToSync.dirFactors[i])) - vtsParams.getParameter ("alpha" + String(i+1))->setValueNotifyingHost (vtsParams.getParameterRange ("alpha" + String(i+1)).convertTo0to1 (paramsToSync.dirFactors[i])); - - if ((int)soloBand[i]->load() != paramsToSync.solo[i]) - vtsParams.getParameter ("solo" + String(i+1))->setValueNotifyingHost (vtsParams.getParameterRange ("solo" + String(i+1)).convertTo0to1 (paramsToSync.solo[i])); - - if ((int)muteBand[i]->load() != paramsToSync.mute[i]) - vtsParams.getParameter ("mute" + String(i+1))->setValueNotifyingHost (vtsParams.getParameterRange ("mute" + String(i+1)).convertTo0to1 (paramsToSync.mute[i])); - - if (!floatsEquivalent(bandGains[i]->load(), paramsToSync.gains[i])) - vtsParams.getParameter ("gain" + String(i+1))->setValueNotifyingHost (vtsParams.getParameterRange ("gain" + String(i+1)).convertTo0to1 (paramsToSync.gains[i])); - - if ((i < 4) && (!floatsEquivalent(xOverFreqs[i]->load(), paramsToSync.xOverFreqs[i]))) - vtsParams.getParameter ("xOverF" + String(i+1))->setValueNotifyingHost (vtsParams.getParameterRange ("xOverF" + String(i+1)).convertTo0to1 (paramsToSync.xOverFreqs[i])); - - - } - - if (!floatsEquivalent(proxDistance->load(), paramsToSync.proximity)) - vtsParams.getParameter ("proximity")->setValueNotifyingHost (vtsParams.getParameterRange ("proximity").convertTo0to1 (paramsToSync.proximity)); - - if ((int)zeroDelayMode->load() != paramsToSync.zeroDelayMode) - vtsParams.getParameter ("zeroDelayMode")->setValueNotifyingHost (vtsParams.getParameterRange ("zeroDelayMode").convertTo0to1 (paramsToSync.zeroDelayMode)); - - if ((int)allowBackwardsPattern->load() != paramsToSync.allowBackwardsPattern) - vtsParams.getParameter ("allowBackwardsPattern")->setValueNotifyingHost (vtsParams.getParameterRange ("allowBackwardsPattern").convertTo0to1 (paramsToSync.allowBackwardsPattern)); - - if ((int)proxOnOff->load() != paramsToSync.proximityOnOff) - vtsParams.getParameter("proximityOnOff")->setValueNotifyingHost(vtsParams.getParameterRange("proximityOnOff").convertTo0to1(paramsToSync.proximityOnOff)); - - if (paramsToSync.ffDfEq != doEq) - { - setEqState(paramsToSync.ffDfEq); - ffDfEqChanged = true; - } - - readingSharedParams = false; - } -} - -void PolarDesignerAudioProcessor::updateLatency() { - if (isBypassed) - { - setLatencySamples(0); - } - else - { - // set delay compensation to FIR_LEN/2-1 if FIR_LEN even and FIR_LEN/2 if odd - if (zeroDelayMode->load() < 0.5f) - setLatencySamples(static_cast (std::ceilf (static_cast (firLen) / 2.0f - 1.0f))); - else - setLatencySamples(0); - } -} - -void PolarDesignerAudioProcessor::changeAbLayerState() -{ - abLayerChanged = true; - ffDfEqChanged = true; - if (abLayerState == 0) - { - layerA = vtsParams.copyState(); - doEqA = doEq; - if (!zeroDelayModeActive()) { oldProxDistanceA = proxDistance->load(); } - readingSharedParams = true; - - vtsParams.state = layerB.createCopy(); - - doEq = doEqB; - zeroDelayModeActive() ? oldProxDistance = 0 : oldProxDistance = oldProxDistanceB; - } - else - { - layerB = vtsParams.copyState(); - doEqB = doEq; - if (!zeroDelayModeActive()) { oldProxDistanceB = proxDistance->load(); } - readingSharedParams = true; - - vtsParams.state = layerA.createCopy(); - - doEq = doEqA; - zeroDelayModeActive() ? oldProxDistance = 0 : oldProxDistance = oldProxDistanceA; - } - vtsParams.state.setProperty("ffDfEq", var(doEq), nullptr); - vtsParams.getParameter ("proximity")->setValueNotifyingHost (vtsParams.getParameter("proximity")->convertTo0to1(oldProxDistance)); - abLayerChanged = false; -} + jassert (getLatencySamples() == 0); -//============================================================================== -// This creates new instances of the plugin.. -AudioProcessor* JUCE_CALLTYPE createPluginFilter() -{ - return new PolarDesignerAudioProcessor(); -} + for (int ch = getMainBusNumInputChannels(); ch < getTotalNumOutputChannels(); ++ch) + buffer.clear (ch, 0, buffer.getNumSamples()); + } + + //============================================================================== + bool PolarDesignerAudioProcessor::hasEditor() const + { + return true; // (change this to false if you choose to not supply an editor) + } + + AudioProcessorEditor* PolarDesignerAudioProcessor::createEditor() + { + return new PolarDesignerAudioProcessorEditor (*this, vtsParams); + } + + //============================================================================== + + void PolarDesignerAudioProcessor::getStateInformation (MemoryBlock& destData) + { + // You should use this method to store your parameters in the memory block. + // You could do that either as raw data, or use the XML or ValueTree classes + // as intermediaries to make it easy to save and load complex data. + vtsParams.state.setProperty ("ffDfEq", var (doEq), nullptr); + vtsParams.state.setProperty ("oldProxDistance", var (oldProxDistance), nullptr); + + if (abLayerState == 1) + { + layerA = vtsParams.copyState(); + doEqA = doEq; + if (proxDistance->load() != 0.0f) + { + oldProxDistanceA = proxDistance->load(); + } + } + if (abLayerState == 0) + { + layerB = vtsParams.copyState(); + doEqB = doEq; + if (proxDistance->load() != 0.0f) + { + oldProxDistanceB = proxDistance->load(); + } + } + + layerA.setProperty ("ffDfEq", var (doEqA), nullptr); + layerA.setProperty ("oldProxDistance", var (oldProxDistanceA), nullptr); + layerB.setProperty ("ffDfEq", var (doEqB), nullptr); + layerB.setProperty ("oldProxDistance", var (oldProxDistanceB), nullptr); + + ValueTree vtsState = vtsParams.copyState(); + ValueTree AState = layerA.createCopy(); + ValueTree BState = layerB.createCopy(); + + saveStates.removeAllChildren (nullptr); + saveStates.addChild (vtsState, 0, nullptr); + saveStates.addChild (AState, 1, nullptr); + saveStates.addChild (BState, 2, nullptr); + + std::unique_ptr xml (saveStates.createXml()); + copyXmlToBinary (*xml, destData); + } + + void PolarDesignerAudioProcessor::setStateInformation (const void* data, int sizeInBytes) + { + // You should use this method to restore your parameters from this memory block, + // whose contents will have been created by the getStateInformation() call. + std::unique_ptr xmlState (getXmlFromBinary (data, sizeInBytes)); + if (xmlState != nullptr) + { + if (xmlState->hasTagName (saveStates.getType())) + { + saveStates = ValueTree::fromXml (*xmlState); + vtsParams.replaceState (saveStates.getChild (1)); + } + else if (xmlState->hasTagName (vtsParams.state.getType())) + { + vtsParams.state = ValueTree::fromXml (*xmlState); + } + } + + layerB = saveStates.getChild (2).createCopy(); + + if (vtsParams.state.hasProperty ("ffDfEq")) + { + Value val = vtsParams.state.getPropertyAsValue ("ffDfEq", nullptr); + if (val.getValue().toString() != "") + { + doEq = static_cast (val.getValue()); + } + } + if (vtsParams.state.hasProperty ("oldProxDistance")) + { + Value val = vtsParams.state.getPropertyAsValue ("oldProxDistance", nullptr); + if (val.getValue().toString() != "") + { + oldProxDistance = static_cast (val.getValue()); + } + } + + if (layerB.hasProperty ("ffDfEq")) + { + Value val = layerB.getPropertyAsValue ("ffDfEq", nullptr); + if (val.getValue().toString() != "") + { + doEqB = static_cast (val.getValue()); + } + } + if (layerB.hasProperty ("oldProxDistance")) + { + Value val = layerB.getPropertyAsValue ("oldProxDistance", nullptr); + if (val.getValue().toString() != "") + { + oldProxDistanceB = static_cast (val.getValue()); + } + } + nBands = static_cast (nBandsPtr->load()) + 1; + activeBandsChanged = true; + zeroDelayModeChanged = true; + ffDfEqChanged = true; + computeAllFilterCoefficients(); + initAllConvolvers(); + repaintDEQ = true; + } + + void PolarDesignerAudioProcessor::parameterChanged (const String& parameterID, float newValue) + { + if (parameterID.startsWith ("xOverF") && !loadingFile) + { + int idx = parameterID.getTrailingIntValue() - 1; + computeFilterCoefficients (idx); + initConvolver (idx); + repaintDEQ = true; + } + else if (parameterID.startsWith ("solo")) + { + soloActive = false; + for (int i = 0; i < nBands; ++i) + { + if (soloBand[i]->load() >= 0.5) + soloActive = true; + } + } + else if (parameterID.startsWith ("alpha")) + { + repaintDEQ = true; + } + else if (parameterID == "nrBands") + { + nBands = static_cast (nBandsPtr->load()) + 1; + resetXoverFreqs(); + activeBandsChanged = true; + computeAllFilterCoefficients(); + initAllConvolvers(); + } + else if (parameterID == "proximity") + { + setProxCompCoefficients (proxDistance->load()); + } + else if (parameterID == "zeroDelayMode") + { + updateLatency(); + + if (newValue == 0.0f) + { + if (abLayerState == 0) + { + vtsParams.getParameter ("proximity")->setValueNotifyingHost (vtsParams.getParameter ("proximity")->convertTo0to1 (oldProxDistanceB)); + } + else + { + vtsParams.getParameter ("proximity")->setValueNotifyingHost (vtsParams.getParameter ("proximity")->convertTo0to1 (oldProxDistanceA)); + } + zeroDelayModeChanged = true; + computeAllFilterCoefficients(); + initAllConvolvers(); + } + else + { + if (abLayerState == 0 && !abLayerChanged.get()) + { + oldProxDistanceB = proxDistance->load(); + } + else if (abLayerState == 1 && !abLayerChanged.get()) + { + oldProxDistanceA = proxDistance->load(); + } + vtsParams.getParameter ("proximity")->setValueNotifyingHost (vtsParams.getParameter ("proximity")->convertTo0to1 (0)); + zeroDelayModeChanged = true; + } + } + else if (parameterID == "syncChannel" && syncChannelPtr->load() >= 0.5f) + { + int ch = (int) syncChannelPtr->load() - 1; + ParamsToSync& paramsToSync = sharedParams.get().syncParams.getReference (ch); + + if (!paramsToSync.paramsValid) // init all params + { + for (int i = 0; i < 5; ++i) + { + paramsToSync.solo[i] = static_cast (soloBand[i]->load()); + paramsToSync.mute[i] = static_cast (muteBand[i]->load()); + paramsToSync.dirFactors[i] = dirFactors[i]->load(); + paramsToSync.gains[i] = bandGains[i]->load(); + + if (i < 4) + paramsToSync.xOverFreqs[i] = xOverFreqs[i]->load(); + } + + paramsToSync.nrActiveBands = static_cast (nBandsPtr->load()); + paramsToSync.proximity = proxDistance->load(); + paramsToSync.proximityOnOff = static_cast (proxOnOff->load()); + + paramsToSync.allowBackwardsPattern = static_cast (allowBackwardsPattern->load()); + + if (!readingSharedParams) + { + paramsToSync.zeroDelayMode = static_cast (zeroDelayMode->load()); + paramsToSync.ffDfEq = doEq; + } + } + } + + // if parameters are synced -> set sharedParams + if (syncChannelPtr->load() >= 0.5f && !readingSharedParams) + { + int ch = (int) syncChannelPtr->load() - 1; + ParamsToSync& paramsToSync = sharedParams.get().syncParams.getReference (ch); + + if (parameterID.startsWith ("xOverF") && !loadingFile) + { + int idx = parameterID.getTrailingIntValue() - 1; + paramsToSync.xOverFreqs[idx] = xOverFreqs[idx]->load(); + } + else if (parameterID.startsWith ("solo")) + { + int idx = parameterID.getTrailingIntValue() - 1; + paramsToSync.solo[idx] = static_cast (soloBand[idx]->load()); + } + else if (parameterID.startsWith ("mute")) + { + int idx = parameterID.getTrailingIntValue() - 1; + paramsToSync.mute[idx] = static_cast (muteBand[idx]->load()); + } + else if (parameterID.startsWith ("alpha")) + { + int idx = parameterID.getTrailingIntValue() - 1; + paramsToSync.dirFactors[idx] = dirFactors[idx]->load(); + } + else if (parameterID == "nrBands") + { + paramsToSync.nrActiveBands = static_cast (nBandsPtr->load()); + } + else if (parameterID == "proximity") + { + paramsToSync.proximity = proxDistance->load(); + } + else if (parameterID == "proximityOnOff") + { + paramsToSync.proximityOnOff = static_cast (proxOnOff->load()); + } + else if (parameterID == "zeroDelayMode") + { + paramsToSync.zeroDelayMode = static_cast (zeroDelayMode->load()); + } + else if (parameterID.startsWith ("gain")) + { + int idx = parameterID.getTrailingIntValue() - 1; + paramsToSync.gains[idx] = bandGains[idx]->load(); + } + else if (parameterID == "allowBackwardsPattern") + { + paramsToSync.allowBackwardsPattern = static_cast (allowBackwardsPattern->load()); + } + } + } + + void PolarDesignerAudioProcessor::setEqState (int idx) + { + doEq = idx; + + if (syncChannelPtr->load() >= 0.5f && !readingSharedParams) + { + int ch = (int) syncChannelPtr->load() - 1; + ParamsToSync& paramsToSync = sharedParams.get().syncParams.getReference (ch); + paramsToSync.ffDfEq = doEq; + } + } + + void PolarDesignerAudioProcessor::setAbLayer (bool state) + { + abLayerState = state; + changeAbLayerState(); + } + + void PolarDesignerAudioProcessor::resetXoverFreqs() + { + switch (nBands) + { + case 1: + break; + + case 2: + for (int i = 0; i < nBands - 1; ++i) + { + vtsParams.getParameter ("xOverF" + String (i + 1))->setValueNotifyingHost (hzToZeroToOne (i, INIT_XOVER_FREQS_2B[i])); + } + break; + + case 3: + for (int i = 0; i < nBands - 1; ++i) + { + vtsParams.getParameter ("xOverF" + String (i + 1))->setValueNotifyingHost (hzToZeroToOne (i, INIT_XOVER_FREQS_3B[i])); + } + break; + + case 4: + for (int i = 0; i < nBands - 1; ++i) + { + vtsParams.getParameter ("xOverF" + String (i + 1))->setValueNotifyingHost (hzToZeroToOne (i, INIT_XOVER_FREQS_4B[i])); + } + break; + + case 5: + for (int i = 0; i < nBands - 1; ++i) + { + vtsParams.getParameter ("xOverF" + String (i + 1))->setValueNotifyingHost (hzToZeroToOne (i, INIT_XOVER_FREQS_5B[i])); + } + break; + + default: + jassert (false); + break; + } + } + + // compute filter bank filter coeffs and store in firFilterBuffer + void PolarDesignerAudioProcessor::computeAllFilterCoefficients() + { + for (int i = 0; i < 4; ++i) + { + computeFilterCoefficients (i); + } + } + + void PolarDesignerAudioProcessor::computeFilterCoefficients (int crossoverNr) + { + // only one band: no filtering + if (nBands == 1) + return; + + // lowest band is simple lowpass + if (crossoverNr == 0) + { + dsp::FilterDesign::FIRCoefficientsPtr lowpass = dsp::FilterDesign::designFIRLowpassWindowMethod (hzFromZeroToOne (0, xOverFreqs[0]->load()), currentSampleRate, static_cast (firLen - 1.0f), dsp::WindowingFunction::WindowingMethod::hamming); + float* lpCoeffs = lowpass->getRawCoefficients(); + firFilterBuffer.copyFrom (0, 0, lpCoeffs, firLen); + } + + // all the other bands are bandpass filters + for (int i = std::max (1, crossoverNr); i < std::min (crossoverNr + 2, nBands - 1); ++i) + { + float halfBandwidth = (hzFromZeroToOne (i, xOverFreqs[i]->load()) - hzFromZeroToOne (i - 1, xOverFreqs[i - 1]->load())) / 2; + dsp::FilterDesign::FIRCoefficientsPtr lp2bp = dsp::FilterDesign::designFIRLowpassWindowMethod (halfBandwidth, currentSampleRate, static_cast (firLen - 1.0f), dsp::WindowingFunction::WindowingMethod::hamming); + float* lp2bpCoeffs = lp2bp->getRawCoefficients(); + auto* filterBufferPointer = firFilterBuffer.getWritePointer (i); + for (int j = 0; j < firLen; j++) // bandpass transform + { + float fCenter = halfBandwidth + hzFromZeroToOne (i - 1, xOverFreqs[i - 1]->load()); + // write bandpass transformed fir coeffs to buffer + *(filterBufferPointer + j) = 2 * *(lp2bpCoeffs + j) * std::cosf (static_cast (MathConstants::twoPi * fCenter / currentSampleRate * (j - (firLen - 1.0f) / 2.0f))); + } + } + + if (crossoverNr == nBands - 2) + { + // highest band is highpass (via frequency transform) + float hpBandwidth = static_cast (currentSampleRate / 2 - hzFromZeroToOne (nBands - 2, xOverFreqs[nBands - 2]->load())); + auto* filterBufferPointer = firFilterBuffer.getWritePointer (nBands - 1); + dsp::FilterDesign::FIRCoefficientsPtr lp2hp = dsp::FilterDesign::designFIRLowpassWindowMethod (hpBandwidth, currentSampleRate, static_cast (firLen - 1.0f), dsp::WindowingFunction::WindowingMethod::hamming); + float* lp2hpCoeffs = lp2hp->getRawCoefficients(); + for (int i = 0; i < firLen; ++i) // highpass transform + { + *(filterBufferPointer + i) = *(lp2hpCoeffs + i) * std::cosf (MathConstants::pi * (i - (firLen - 1.0f) / 2.0f)); + } + } + } + + void PolarDesignerAudioProcessor::initAllConvolvers() + { + convolversReady = false; + + // build filters and fill firFilterBuffer + dsp::AudioBlock convBlk (firFilterBuffer); + dsp::ProcessSpec convSpec { currentSampleRate, static_cast (currentBlockSize), 1 }; + for (int i = 0; i < nBands; ++i) // prepare nBands mono convolvers + { + AudioBuffer convSingleBuffOmni (1, firLen); + convSingleBuffOmni.copyFrom (0, 0, firFilterBuffer, i, 0, firLen); + + AudioBuffer convSingleBuffEight (1, firLen); + convSingleBuffEight.copyFrom (0, 0, firFilterBuffer, i, 0, firLen); + + // omni convolver + convolvers[2 * i].prepare (convSpec); // must be called before loading IR + convolvers[2 * i].loadImpulseResponse (std::move (convSingleBuffOmni), currentSampleRate, Convolution::Stereo::no, Convolution::Trim::no, Convolution::Normalise::no); + + // eight convolver + convolvers[2 * i + 1].prepare (convSpec); // must be called before loading IR + convolvers[2 * i + 1].loadImpulseResponse (std::move (convSingleBuffEight), currentSampleRate, Convolution::Stereo::no, Convolution::Trim::no, Convolution::Normalise::no); + } + convolversReady = true; + } + + void PolarDesignerAudioProcessor::initConvolver (int convNr) + { + convolversReady = false; + + // build filters and fill firFilterBuffer + dsp::AudioBlock convBlk (firFilterBuffer); + dsp::ProcessSpec convSpec { currentSampleRate, static_cast (currentBlockSize), 1 }; + + // update two convolvers: if one crossover frequency changes, two neighbouring bands need new filters + for (int i = convNr; i < convNr + 2; ++i) + { + AudioBuffer convSingleBuffOmni (1, firLen); + convSingleBuffOmni.copyFrom (0, 0, firFilterBuffer, i, 0, firLen); + + AudioBuffer convSingleBuffEight (1, firLen); + convSingleBuffEight.copyFrom (0, 0, firFilterBuffer, i, 0, firLen); + + // omni convolver + convolvers[2 * i].prepare (convSpec); // must be called before loading IR + convolvers[2 * i].loadImpulseResponse (std::move (convSingleBuffOmni), currentSampleRate, Convolution::Stereo::no, Convolution::Trim::no, Convolution::Normalise::no); + + // eight convolver + convolvers[2 * i + 1].prepare (convSpec); // must be called before loading IR + convolvers[2 * i + 1].loadImpulseResponse (std::move (convSingleBuffEight), currentSampleRate, Convolution::Stereo::no, Convolution::Trim::no, Convolution::Normalise::no); + } + convolversReady = true; + } + + void PolarDesignerAudioProcessor::createOmniAndEightSignals (AudioBuffer& buffer) + { + int numSamples = buffer.getNumSamples(); + // calculate omni part + const float* readPointerFront = buffer.getReadPointer (0); + const float* readPointerBack = buffer.getReadPointer (1); + float* writePointerOmni = omniEightBuffer.getWritePointer (0); + FloatVectorOperations::copy (writePointerOmni, readPointerFront, numSamples); + FloatVectorOperations::add (writePointerOmni, readPointerBack, numSamples); + + // calculate fig-of-eight part + float* writePointerEight = omniEightBuffer.getWritePointer (1); + FloatVectorOperations::copy (writePointerEight, readPointerFront, numSamples); + FloatVectorOperations::subtract (writePointerEight, readPointerBack, numSamples); + } + + void PolarDesignerAudioProcessor::createPolarPatterns (AudioBuffer& buffer) + { + int numSamples = buffer.getNumSamples(); + buffer.clear(); + + int nActiveBands = nBands; + if (zeroDelayMode->load() > 0.5f) + nActiveBands = 1; + + for (int i = 0; i < nActiveBands; ++i) + { + if ((muteBand[i]->load() > 0.5 && soloBand[i]->load() < 0.5) || (soloActive && soloBand[i]->load() < 0.5)) + continue; + + // calculate patterns and add to output buffer + const float* readPointerOmni = filterBankBuffer.getReadPointer (2 * i); + const float* readPointerEight = filterBankBuffer.getReadPointer (2 * i + 1); + + float oldGain = Decibels::decibelsToGain (oldBandGains[i], -59.91f); + float gain = Decibels::decibelsToGain (bandGains[i]->load(), -59.91f); + + // add with ramp to prevent crackling noises + buffer.addFromWithRamp (0, 0, readPointerOmni, numSamples, (1 - std::abs (oldDirFactors[i])) * oldGain, (1 - std::abs (dirFactors[i]->load())) * gain); + buffer.addFromWithRamp (0, 0, readPointerEight, numSamples, oldDirFactors[i] * oldGain, dirFactors[i]->load() * gain); + + oldDirFactors[i] = dirFactors[i]->load(); + oldBandGains[i] = bandGains[i]->load(); + } + + // delay needs to be running constantly to prevent clicks + delayBuffer.copyFrom (0, 0, buffer, 0, 0, numSamples); + dsp::AudioBlock delayBlock (delayBuffer); + dsp::ProcessContextReplacing delayContext (delayBlock); + delay.process (delayContext); + + if (nActiveBands == 1 && zeroDelayMode->load() < 0.5f) + { + buffer.copyFrom (0, 0, delayBuffer, 0, 0, numSamples); + } + + // copy to second output channel -> this generates loud glitches in pro tools if mono output configuration is used + // -> check getMainBusNumOutputChannels() + if (buffer.getNumChannels() == 2 && getMainBusNumOutputChannels() == 2) + buffer.copyFrom (1, 0, buffer, 0, 0, numSamples); + } + + void PolarDesignerAudioProcessor::setLastDir (File newLastDir) + { + lastDir = newLastDir; + const var v (lastDir.getFullPathName()); + properties->setValue ("presetFolder", v); + } + + Result PolarDesignerAudioProcessor::loadPreset (const File& presetFile) + { + var parsedJson; + if (!presetFile.exists()) + return Result::fail ("File does not exist!"); + + String jsonString = presetFile.loadFileAsString(); + Result result = JSON::parse (jsonString, parsedJson); + if (!result.wasOk()) + return Result::fail ("File could not be parsed: Please provide valid JSON!"); + + for (auto& it : presetProperties) + { + if (!parsedJson.hasProperty (it)) + return Result::fail ("Corrupt preset file: No '" + it + "' property found."); + } + + loadingFile = true; + + float x = parsedJson.getProperty ("nrActiveBands", parsedJson); + vtsParams.getParameter ("nrBands")->setValueNotifyingHost (vtsParams.getParameter ("nrBands")->convertTo0to1 (x - 1)); + + for (int i = 0; i < 4; ++i) + { + x = parsedJson.getProperty ("xOverF" + String (i + 1), parsedJson); + vtsParams.getParameter ("xOverF" + String (i + 1))->setValueNotifyingHost (hzToZeroToOne (i, x)); + } + + NormalisableRange dfRange = vtsParams.getParameter ("alpha1")->getNormalisableRange(); + + for (int i = 0; i < 5; ++i) + { + x = parsedJson.getProperty ("dirFactor" + String (i + 1), parsedJson); + if (x < dfRange.start || x > dfRange.end) + return Result::fail ("DirFactor" + String (i + 1) + " needs to be between " + String (dfRange.start) + " and " + String (dfRange.end) + "."); + vtsParams.getParameter ("alpha" + String (i + 1))->setValueNotifyingHost (dfRange.convertTo0to1 (x)); + + x = parsedJson.getProperty ("gain" + String (i + 1), parsedJson); + vtsParams.getParameter ("gain" + String (i + 1))->setValueNotifyingHost (vtsParams.getParameter ("gain1")->convertTo0to1 (x)); + + x = parsedJson.getProperty ("solo" + String (i + 1), parsedJson); + vtsParams.getParameter ("solo" + String (i + 1))->setValueNotifyingHost (vtsParams.getParameter ("solo1")->convertTo0to1 (x)); + + x = parsedJson.getProperty ("mute" + String (i + 1), parsedJson); + vtsParams.getParameter ("mute" + String (i + 1))->setValueNotifyingHost (vtsParams.getParameter ("solo1")->convertTo0to1 (x)); + } + + doEq = parsedJson.getProperty ("ffDfEq", parsedJson); + + x = parsedJson.getProperty ("proximity", parsedJson); + vtsParams.getParameter ("proximity")->setValueNotifyingHost (vtsParams.getParameter ("proximity")->convertTo0to1 (x)); + + x = parsedJson.getProperty ("proximityOnOff", parsedJson); + vtsParams.getParameter ("proximityOnOff")->setValueNotifyingHost (vtsParams.getParameter ("proximityOnOff")->convertTo0to1 (x)); + + x = parsedJson.getProperty ("trimPosition", parsedJson); + vtsParams.getParameter ("trimPosition")->setValueNotifyingHost (vtsParams.getParameter ("trimPosition")->convertTo0to1 (x)); + + + loadingFile = false; + + // set parameters + nBands = static_cast (nBandsPtr->load()) + 1; + activeBandsChanged = true; + computeAllFilterCoefficients(); + initAllConvolvers(); + repaintDEQ = true; + + return Result::ok(); + } + + Result PolarDesignerAudioProcessor::savePreset (File destination) + { + DynamicObject* jsonObj = new DynamicObject(); + char versionString[10]; + strcpy (versionString, "v"); + strcat (versionString, JucePlugin_VersionString); + jsonObj->setProperty ("Description", var ("This preset file was created with the Austrian Audio PolarDesigner plugin " + String (versionString) + ", for more information see www.austrian.audio .")); + jsonObj->setProperty ("nrActiveBands", nBands); + jsonObj->setProperty ( "trimPosition", trimPosition->load()); + jsonObj->setProperty ("xOverF1", static_cast (hzFromZeroToOne (0, xOverFreqs[0]->load()))); + jsonObj->setProperty ("xOverF2", static_cast (hzFromZeroToOne (1, xOverFreqs[1]->load()))); + jsonObj->setProperty ("xOverF3", static_cast (hzFromZeroToOne (2, xOverFreqs[2]->load()))); + jsonObj->setProperty ("xOverF4", static_cast (hzFromZeroToOne (3, xOverFreqs[3]->load()))); + jsonObj->setProperty ("dirFactor1", dirFactors[0]->load()); + jsonObj->setProperty ("dirFactor2", dirFactors[1]->load()); + jsonObj->setProperty ("dirFactor3", dirFactors[2]->load()); + jsonObj->setProperty ("dirFactor4", dirFactors[3]->load()); + jsonObj->setProperty ("dirFactor5", dirFactors[4]->load()); + jsonObj->setProperty ("gain1", bandGains[0]->load()); + jsonObj->setProperty ("gain2", bandGains[1]->load()); + jsonObj->setProperty ("gain3", bandGains[2]->load()); + jsonObj->setProperty ("gain4", bandGains[3]->load()); + jsonObj->setProperty ("gain5", bandGains[4]->load()); + jsonObj->setProperty ("solo1", soloBand[0]->load()); + jsonObj->setProperty ("solo2", soloBand[1]->load()); + jsonObj->setProperty ("solo3", soloBand[2]->load()); + jsonObj->setProperty ("solo4", soloBand[3]->load()); + jsonObj->setProperty ("solo5", soloBand[4]->load()); + jsonObj->setProperty ("mute1", muteBand[0]->load()); + jsonObj->setProperty ("mute2", muteBand[1]->load()); + jsonObj->setProperty ("mute3", muteBand[2]->load()); + jsonObj->setProperty ("mute4", muteBand[3]->load()); + jsonObj->setProperty ("mute5", muteBand[4]->load()); + jsonObj->setProperty ("ffDfEq", doEq); + jsonObj->setProperty ("proximity", proxDistance->load()); + jsonObj->setProperty ("proximityOnOff", proxOnOff->load()); + + String jsonString = JSON::toString (var (jsonObj), false, 2); + if (destination.replaceWithText (jsonString)) + return Result::ok(); + else + return Result::fail ("Could not write preset file. Check file access permissions."); + } + + float PolarDesignerAudioProcessor::hzToZeroToOne (int idx, float hz) + { + switch (nBands) + { + case 1: + return 0; + break; + + case 2: + return (hz - XOVER_RANGE_START_2B[idx]) / (XOVER_RANGE_END_2B[idx] - XOVER_RANGE_START_2B[idx]); + break; + + case 3: + return (hz - XOVER_RANGE_START_3B[idx]) / (XOVER_RANGE_END_3B[idx] - XOVER_RANGE_START_3B[idx]); + break; + + case 4: + return (hz - XOVER_RANGE_START_4B[idx]) / (XOVER_RANGE_END_4B[idx] - XOVER_RANGE_START_4B[idx]); + break; + + case 5: + return (hz - XOVER_RANGE_START_5B[idx]) / (XOVER_RANGE_END_5B[idx] - XOVER_RANGE_START_5B[idx]); + break; + + default: + jassert (false); + break; + } + return 0; + } + + float PolarDesignerAudioProcessor::hzFromZeroToOne (int idx, float val) + { + switch (nBands) + { + case 1: + return 0; + break; + + case 2: + return XOVER_RANGE_START_2B[idx] + val * (XOVER_RANGE_END_2B[idx] - XOVER_RANGE_START_2B[idx]); + break; + + case 3: + return XOVER_RANGE_START_3B[idx] + val * (XOVER_RANGE_END_3B[idx] - XOVER_RANGE_START_3B[idx]); + break; + + case 4: + return XOVER_RANGE_START_4B[idx] + val * (XOVER_RANGE_END_4B[idx] - XOVER_RANGE_START_4B[idx]); + break; + + case 5: + return XOVER_RANGE_START_5B[idx] + val * (XOVER_RANGE_END_5B[idx] - XOVER_RANGE_START_5B[idx]); + break; + + default: + jassert (false); + break; + } + return 0; + } + + float PolarDesignerAudioProcessor::getXoverSliderRangeStart (int sliderNum) + { + switch (nBands) + { + case 2: + return XOVER_RANGE_START_2B[sliderNum]; + break; + + case 3: + return XOVER_RANGE_START_3B[sliderNum]; + break; + + case 4: + return XOVER_RANGE_START_4B[sliderNum]; + break; + + case 5: + return XOVER_RANGE_START_5B[sliderNum]; + break; + + default: + jassert (false); + break; + } + return 0; + } + + float PolarDesignerAudioProcessor::getXoverSliderRangeEnd (int sliderNum) + { + switch (nBands) + { + case 2: + return XOVER_RANGE_END_2B[sliderNum]; + break; + + case 3: + return XOVER_RANGE_END_3B[sliderNum]; + break; + + case 4: + return XOVER_RANGE_END_4B[sliderNum]; + break; + + case 5: + return XOVER_RANGE_END_5B[sliderNum]; + break; + + default: + jassert (false); + break; + } + return 0; + } + + void PolarDesignerAudioProcessor::startTracking (bool trackDisturber) + { + if (trackDisturber) + { + trackingDisturber = true; + for (int i = 0; i < 5; ++i) + { + omniSqSumDist[i] = 0.0f; + eightSqSumDist[i] = 0.0f; + omniEightSumDist[i] = 0.0f; + } + } + else + { + trackingDisturber = false; + for (int i = 0; i < 5; ++i) + { + omniSqSumSig[i] = 0.0f; + eightSqSumSig[i] = 0.0f; + omniEightSumSig[i] = 0.0f; + } + } + + nrBlocksRecorded = 0; + trackingActive = true; + } + + void PolarDesignerAudioProcessor::stopTracking (int applyOptimalPattern) + { + trackingActive = false; + if (applyOptimalPattern == 1) + { + if (trackingDisturber) + { + if (nrBlocksRecorded != 0) + { + for (int i = 0; i < 5; ++i) + { + omniSqSumDist[i] = omniSqSumDist[i] / nrBlocksRecorded; + eightSqSumDist[i] = eightSqSumDist[i] / nrBlocksRecorded; + omniEightSumDist[i] = omniEightSumDist[i] / nrBlocksRecorded; + } + } + setMinimumDisturbancePattern(); + } + else + { + if (nrBlocksRecorded != 0) + { + for (int i = 0; i < 5; ++i) + { + omniSqSumSig[i] = omniSqSumSig[i] / nrBlocksRecorded; + eightSqSumSig[i] = eightSqSumSig[i] / nrBlocksRecorded; + omniEightSumSig[i] = omniEightSumSig[i] / nrBlocksRecorded; + } + } + setMaximumSignalPattern(); + } + } + else if (applyOptimalPattern == 2) // max sig-to-dist + { + if (trackingDisturber) + { + if (nrBlocksRecorded != 0) + { + for (int i = 0; i < 5; ++i) + { + omniSqSumDist[i] = omniSqSumDist[i] / nrBlocksRecorded; + eightSqSumDist[i] = eightSqSumDist[i] / nrBlocksRecorded; + omniEightSumDist[i] = omniEightSumDist[i] / nrBlocksRecorded; + } + } + disturberRecorded = true; + } + else + { + if (nrBlocksRecorded != 0) + { + for (int i = 0; i < 5; ++i) + { + omniSqSumSig[i] = omniSqSumSig[i] / nrBlocksRecorded; + eightSqSumSig[i] = eightSqSumSig[i] / nrBlocksRecorded; + omniEightSumSig[i] = omniEightSumSig[i] / nrBlocksRecorded; + } + } + signalRecorded = true; + } + maximizeSigToDistRatio(); + } + } + + void PolarDesignerAudioProcessor::trackSignalEnergy() + { + int numSamples = filterBankBuffer.getNumSamples(); + for (int i = 0; i < nBands; ++i) + { + const float* readPointerOmni = filterBankBuffer.getReadPointer (2 * i); + const float* readPointerEight = filterBankBuffer.getReadPointer (2 * i + 1); + if (trackingDisturber) + { + for (int j = 0; j < numSamples; ++j) + { + float omniSample = readPointerOmni[j]; + omniSqSumDist[i] += omniSample * omniSample / numSamples; + float eightSample = readPointerEight[j]; + eightSqSumDist[i] += eightSample * eightSample / numSamples; + omniEightSumDist[i] += omniSample * eightSample / numSamples; + } + } + else + { + for (int j = 0; j < numSamples; ++j) + { + float omniSample = readPointerOmni[j]; + omniSqSumSig[i] += omniSample * omniSample / numSamples; + float eightSample = readPointerEight[j]; + eightSqSumSig[i] += eightSample * eightSample / numSamples; + omniEightSumSig[i] += omniSample * eightSample / numSamples; + } + } + } + ++nrBlocksRecorded; + } + + void PolarDesignerAudioProcessor::setMinimumDisturbancePattern() + { + float disturberPower = 0.0f; + float minPowerAlpha = 0.0f; + float alphaStart = 0.0f; + if (allowBackwardsPattern->load() == 1.0f) + alphaStart = -0.5f; + + for (int i = 0; i < nBands; ++i) + { + for (float alpha = alphaStart; alpha <= 1.0f; alpha += 0.01f) + { + float currentPower = std::pow ((1.0f - std::abs (alpha)), 2.0f) * omniSqSumDist[i] + std::pow (alpha, 2.0f) * eightSqSumDist[i] + 2.0f * (1.0f - std::abs (alpha)) * alpha * omniEightSumDist[i]; + if ((floatsEquivalent (alpha, alphaStart)) || (currentPower < disturberPower)) + { + disturberPower = currentPower; + minPowerAlpha = alpha; + } + } + if (disturberPower != 0.0f) // do not apply changes, if playback is not active + { + vtsParams.getParameter ("alpha" + String (i + 1))->setValueNotifyingHost (vtsParams.getParameter ("alpha1")->convertTo0to1 (minPowerAlpha)); + disturberRecorded = true; + } + } + } + + void PolarDesignerAudioProcessor::setMaximumSignalPattern() + { + float signalPower = 0.0f; + float maxPowerAlpha = 0.0f; + float alphaStart = 0.0f; + if (allowBackwardsPattern->load() == 1.0f) + alphaStart = -0.5f; + + for (int i = 0; i < nBands; ++i) + { + for (float alpha = alphaStart; alpha <= 1.0f; alpha += 0.01f) + { + float currentPower = std::pow ((1.0f - std::abs (alpha)), 2.0f) * omniSqSumSig[i] + std::pow (alpha, 2.0f) * eightSqSumSig[i] + 2.0f * (1.0f - std::abs (alpha)) * alpha * omniEightSumSig[i]; + if ((floatsEquivalent (alpha, alphaStart)) || currentPower > signalPower) + { + signalPower = currentPower; + maxPowerAlpha = alpha; + } + } + if (signalPower != 0.0f) + { + vtsParams.getParameter ("alpha" + String (i + 1))->setValueNotifyingHost (vtsParams.getParameter ("alpha1")->convertTo0to1 (maxPowerAlpha)); + signalRecorded = true; + } + } + } + + void PolarDesignerAudioProcessor::maximizeSigToDistRatio() + { + float distToSigRatio = 0.0f; + float maxDistToSigAlpha = 0.0f; + float alphaStart = 0.0f; + if (allowBackwardsPattern->load() == 1.0f) + alphaStart = -0.5f; + + for (int i = 0; i < nBands; ++i) + { + for (float alpha = alphaStart; alpha <= 1.0f; alpha += 0.01f) + { + float currentSigPower = std::pow ((1.0f - std::abs (alpha)), 2.0f) * omniSqSumSig[i] + std::pow (alpha, 2.0f) * eightSqSumSig[i] + 2.0f * (1.0f - std::abs (alpha)) * alpha * omniEightSumSig[i]; + float currentDistPower = std::pow ((1.0f - std::abs (alpha)), 2.0f) * omniSqSumDist[i] + std::pow (alpha, 2.0f) * eightSqSumDist[i] + 2.0f * (1.0f - std::abs (alpha)) * alpha * omniEightSumDist[i]; + float currentRatio; + if (currentDistPower == 0.0f) + currentRatio = 0.0f; + else + currentRatio = currentSigPower / currentDistPower; + + if ((floatsEquivalent (alpha, alphaStart)) || currentRatio > distToSigRatio) + { + distToSigRatio = currentRatio; + maxDistToSigAlpha = alpha; + } + } + if (distToSigRatio != 0.0f) + vtsParams.getParameter ("alpha" + String (i + 1))->setValueNotifyingHost (vtsParams.getParameter ("alpha1")->convertTo0to1 (maxDistToSigAlpha)); + } + } + + void PolarDesignerAudioProcessor::setProxCompCoefficients (float distance) + { + // The speed of sound at standard air pressure/20C - meters per second + const float c = 343.0f; + float fs = (float) getSampleRate(); + + // float b0 = -c / (fs * 4 * distance) + 1; + // float b1 = -exp(-c / (fs * 2 * distance)) * (1 + c / (fs * 4 * distance)); + // float a0 = 1; + // float a1 = -exp(-c / (fs * 2 * distance)); + + // use logarithmic fader impact: equation is for fader between -1.0 .. 1.0 + // returns values between 1 .. 0.1 + float a = (0.05f - 1.0f) / (-log (1.1f) + log (0.1f)); + float b = 1.0f + a * log (0.1f); + float r = -a * log (std::abs (distance) + 0.1f) + b; + + float b0, b1, a0, a1; + + // normalized to r_ref = 1m + if (distance <= 0) //bass cut + { + if (r < 0.01f) + r = 0.01f; + + b0 = c * (r - 1.0f) / (fs * 2.0f * r) + 1.0f; + b1 = -exp (-c / (fs * r)) * (1.0f - c * (r - 1.0f) / (fs * 2.0f * r)); + a0 = 1.0f; + a1 = -exp (-c / (fs * r)); + } + else // bass boost, careful: instable for r<0.05 + { + if (r < 0.05f) + r = 0.05f; + + b0 = c * (1.0f - r) / (fs * 2.0f * r) + 1.0f; + b1 = -exp (-c / fs) * (1.0f - c * (1.0f - r) / (fs * 2.0f * r)); + a0 = 1.0f; + a1 = -exp (-c / fs); + } + + *proxCompIIR.coefficients = dsp::IIR::Coefficients (b0, b1, a0, a1); + } + + void PolarDesignerAudioProcessor::timerCallback() + { + if (syncChannelPtr->load() > 0.5f) + { + readingSharedParams = true; + + int ch = (int) syncChannelPtr->load() - 1; + ParamsToSync& paramsToSync = sharedParams.get().syncParams.getReference (ch); + + if ((int) nBandsPtr->load() != paramsToSync.nrActiveBands) + vtsParams.getParameter ("nrBands")->setValueNotifyingHost (vtsParams.getParameterRange ("nrBands").convertTo0to1 (paramsToSync.nrActiveBands)); + + for (int i = 0; i < 5; ++i) + { + if (!floatsEquivalent (dirFactors[i]->load(), paramsToSync.dirFactors[i])) + vtsParams.getParameter ("alpha" + String (i + 1))->setValueNotifyingHost (vtsParams.getParameterRange ("alpha" + String (i + 1)).convertTo0to1 (paramsToSync.dirFactors[i])); + + if ((int) soloBand[i]->load() != paramsToSync.solo[i]) + vtsParams.getParameter ("solo" + String (i + 1))->setValueNotifyingHost (vtsParams.getParameterRange ("solo" + String (i + 1)).convertTo0to1 (paramsToSync.solo[i])); + + if ((int) muteBand[i]->load() != paramsToSync.mute[i]) + vtsParams.getParameter ("mute" + String (i + 1))->setValueNotifyingHost (vtsParams.getParameterRange ("mute" + String (i + 1)).convertTo0to1 (paramsToSync.mute[i])); + + if (!floatsEquivalent (bandGains[i]->load(), paramsToSync.gains[i])) + vtsParams.getParameter ("gain" + String (i + 1))->setValueNotifyingHost (vtsParams.getParameterRange ("gain" + String (i + 1)).convertTo0to1 (paramsToSync.gains[i])); + + if ((i < 4) && (!floatsEquivalent (xOverFreqs[i]->load(), paramsToSync.xOverFreqs[i]))) + vtsParams.getParameter ("xOverF" + String (i + 1))->setValueNotifyingHost (vtsParams.getParameterRange ("xOverF" + String (i + 1)).convertTo0to1 (paramsToSync.xOverFreqs[i])); + } + + if (!floatsEquivalent (proxDistance->load(), paramsToSync.proximity)) + vtsParams.getParameter ("proximity")->setValueNotifyingHost (vtsParams.getParameterRange ("proximity").convertTo0to1 (paramsToSync.proximity)); + + if ((int) zeroDelayMode->load() != paramsToSync.zeroDelayMode) + vtsParams.getParameter ("zeroDelayMode")->setValueNotifyingHost (vtsParams.getParameterRange ("zeroDelayMode").convertTo0to1 (paramsToSync.zeroDelayMode)); + + if ((int) allowBackwardsPattern->load() != paramsToSync.allowBackwardsPattern) + vtsParams.getParameter ("allowBackwardsPattern")->setValueNotifyingHost (vtsParams.getParameterRange ("allowBackwardsPattern").convertTo0to1 (paramsToSync.allowBackwardsPattern)); + + if ((int) proxOnOff->load() != paramsToSync.proximityOnOff) + vtsParams.getParameter ("proximityOnOff")->setValueNotifyingHost (vtsParams.getParameterRange ("proximityOnOff").convertTo0to1 (paramsToSync.proximityOnOff)); + + if (paramsToSync.ffDfEq != doEq) + { + setEqState (paramsToSync.ffDfEq); + ffDfEqChanged = true; + } + + readingSharedParams = false; + } + } + + void PolarDesignerAudioProcessor::updateLatency() + { + if (isBypassed) + { + setLatencySamples (0); + } + else + { + // set delay compensation to FIR_LEN/2-1 if FIR_LEN even and FIR_LEN/2 if odd + if (zeroDelayMode->load() < 0.5f) + setLatencySamples (static_cast (std::ceilf (static_cast (firLen) / 2.0f - 1.0f))); + else + setLatencySamples (0); + } + } + + void PolarDesignerAudioProcessor::changeAbLayerState() + { + abLayerChanged = true; + ffDfEqChanged = true; + if (abLayerState == 0) + { + layerA = vtsParams.copyState(); + doEqA = doEq; + if (!zeroDelayModeActive()) + { + oldProxDistanceA = proxDistance->load(); + } + readingSharedParams = true; + + vtsParams.state = layerB.createCopy(); + + doEq = doEqB; + zeroDelayModeActive() ? oldProxDistance = 0 : oldProxDistance = oldProxDistanceB; + } + else + { + layerB = vtsParams.copyState(); + doEqB = doEq; + if (!zeroDelayModeActive()) + { + oldProxDistanceB = proxDistance->load(); + } + readingSharedParams = true; + + vtsParams.state = layerA.createCopy(); + + doEq = doEqA; + zeroDelayModeActive() ? oldProxDistance = 0 : oldProxDistance = oldProxDistanceA; + } + vtsParams.state.setProperty ("ffDfEq", var (doEq), nullptr); + vtsParams.getParameter ("proximity")->setValueNotifyingHost (vtsParams.getParameter ("proximity")->convertTo0to1 (oldProxDistance)); + abLayerChanged = false; + } + + //============================================================================== + // This creates new instances of the plugin.. + AudioProcessor* JUCE_CALLTYPE createPluginFilter() + { + return new PolarDesignerAudioProcessor(); + } diff --git a/source/PluginProcessor.h b/source/PluginProcessor.h index f8f3c60..47ddf4e 100644 --- a/source/PluginProcessor.h +++ b/source/PluginProcessor.h @@ -152,7 +152,7 @@ class PolarDesignerAudioProcessor : public AudioProcessor, public AudioProcesso float oldProxDistanceA = 0; float oldProxDistanceB = 0; Atomic abLayerChanged = false; - + bool convolversReady; AudioVisualiserComponent termControlWaveform; @@ -232,7 +232,9 @@ class PolarDesignerAudioProcessor : public AudioProcessor, public AudioProcesso std::atomic* zeroDelayMode; std::atomic* soloBand[5]; std::atomic* muteBand[5]; - + + std::atomic *trimPosition; + bool isBypassed; bool soloActive; bool loadingFile;