AnalogTapeModel

Physical modelling signal processing for analog tape recording
Log | Files | Refs | Submodules | README | LICENSE

commit 9d8c97a902ba5eeac9be18d423e2a2fd8d0e37ea
parent 292e0005300149e714265e743bfe0cfc745b3a01
Author: jatinchowdhury18 <jatinchowdhury18@gmail.com>
Date:   Sun, 14 Jun 2020 17:24:56 -0700

Fix phase cancellation issues, latency reporting (#43)

* Fix phase cancellation issues, latency reporting

* Update pluginval

* Adjust plugivan commands

Co-authored-by: jatinchowdhury18 <jatinchowdhury18@users.noreply.github.com>
Diffstat:
M.travis.yml | 13++++++-------
MPlugin/Source/PluginProcessor.cpp | 23+++++++++++++++++++++++
MPlugin/Source/PluginProcessor.h | 3+++
MPlugin/Source/Processors/Hysteresis/HysteresisProcessor.cpp | 6++++++
MPlugin/Source/Processors/Hysteresis/HysteresisProcessor.h | 2++
MPlugin/Source/Processors/Timing_Effects/DelayProcessor.cpp | 39---------------------------------------
MPlugin/Source/Processors/Timing_Effects/DelayProcessor.h | 42+++++++++++++++++++++++++++++++++++++++---
7 files changed, 79 insertions(+), 49 deletions(-)

diff --git a/.travis.yml b/.travis.yml @@ -99,24 +99,23 @@ script: if [[ $TRAVIS_OS_NAME == 'osx' ]]; then echo "Validating plugin" cd $TRAVIS_BUILD_DIR/ - curl -L "https://github.com/Tracktion/pluginval/releases/download/v0.2.3/pluginval_macOS.zip" -o pluginval.zip + curl -L "https://github.com/Tracktion/pluginval/releases/download/latest_release/pluginval_macOS.zip" -o pluginval.zip unzip pluginval - pluginval.app/Contents/MacOS/pluginval --strictness-level 5 --validate-in-process --validate "$TRAVIS_BUILD_DIR/Plugin/Builds/MacOSX/build/Release/CHOWTapeModel.vst3" || exit 1 - pluginval.app/Contents/MacOS/pluginval --strictness-level 5 --validate-in-process --validate "$TRAVIS_BUILD_DIR/Plugin/Builds/MacOSX/build/Release/CHOWTapeModel.component" || exit 1 + pluginval.app/Contents/MacOS/pluginval --strictness-level 8 --validate-in-process --validate "$TRAVIS_BUILD_DIR/Plugin/Builds/MacOSX/build/Release/CHOWTapeModel.vst3" || exit 1 + # pluginval.app/Contents/MacOS/pluginval --strictness-level 8 --validate-in-process --validate "$TRAVIS_BUILD_DIR/Plugin/Builds/MacOSX/build/Release/CHOWTapeModel.component" || exit 1 fi # - | # if [[ $TRAVIS_OS_NAME == 'windows' ]]; then # echo "Validating plugin" # cd $TRAVIS_BUILD_DIR/ - # powershell -Command "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest https://github.com/Tracktion/pluginval/releases/download/latest_release/pluginval_Windows.zip -OutFile pluginval.zip" - # powershell -Command "Expand-Archive pluginval.zip -DestinationPath ." - # ./pluginval.exe --strictness-level 8 --validate-in-process --validate "$TRAVIS_BUILD_DIR/Plugin/Builds/VisualStudio2017/x64/Debug/VST3/CHOWTapeModel.vst3" + # choco install pluginval + # pluginval.exe --strictness-level 8 --validate-in-process --validate "$TRAVIS_BUILD_DIR/Plugin/Builds/VisualStudio2017/x64/Debug/VST3/CHOWTapeModel.vst3" # fi - | if [[ $TRAVIS_OS_NAME == 'linux' ]]; then curl -L "https://github.com/Tracktion/pluginval/releases/download/latest_release/pluginval_Linux.zip" -o pluginval.zip unzip pluginval - ./pluginval --strictness-level 5 --validate-in-process --timeout-ms 500000 --validate "$TRAVIS_BUILD_DIR/Plugin/Builds/LinuxMakefile/build/CHOWTapeModel.so" || exit 1 + ./pluginval --strictness-level 8 --validate-in-process --timeout-ms 500000 --validate "$TRAVIS_BUILD_DIR/Plugin/Builds/LinuxMakefile/build/CHOWTapeModel.so" || exit 1 fi - echo "SUCCESS" diff --git a/Plugin/Source/PluginProcessor.cpp b/Plugin/Source/PluginProcessor.cpp @@ -131,13 +131,18 @@ void ChowtapeModelAudioProcessor::changeProgramName (int index, const String& ne //============================================================================== void ChowtapeModelAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) { + setRateAndBufferSizeDetails (sampleRate, samplesPerBlock); + inGain.prepareToPlay (sampleRate, samplesPerBlock); hysteresis.prepareToPlay (sampleRate, samplesPerBlock); degrade.prepareToPlay (sampleRate, samplesPerBlock); chewer.prepare (sampleRate); for (int ch = 0; ch < 2; ++ch) + { + dryDelay[ch].prepareToPlay (sampleRate, samplesPerBlock); lossFilter[ch]->prepare ((float) sampleRate, samplesPerBlock); + } flutter.prepareToPlay (sampleRate, samplesPerBlock); outGain.prepareToPlay (sampleRate, samplesPerBlock); @@ -147,6 +152,8 @@ void ChowtapeModelAudioProcessor::prepareToPlay (double sampleRate, int samplesP dryWet.setDryWet (*vts.getRawParameterValue ("drywet")); dryWet.reset(); dryBuffer.setSize (2, samplesPerBlock); + + setLatencySamples (roundToInt (calcLatencySamples())); } void ChowtapeModelAudioProcessor::releaseResources() @@ -154,6 +161,11 @@ void ChowtapeModelAudioProcessor::releaseResources() hysteresis.releaseResources(); } +float ChowtapeModelAudioProcessor::calcLatencySamples() const noexcept +{ + return hysteresis.getLatencySamples(); +} + #ifndef JucePlugin_PreferredChannelConfigurations bool ChowtapeModelAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const { @@ -199,6 +211,17 @@ void ChowtapeModelAudioProcessor::processBlock (AudioBuffer<float>& buffer, Midi for (int ch = 0; ch < buffer.getNumChannels(); ++ch) lossFilter[ch]->processBlock (buffer.getWritePointer (ch), buffer.getNumSamples()); + // delay dry buffer to avoid phase issues + setLatencySamples (roundToInt (calcLatencySamples())); + for (int ch = 0; ch < buffer.getNumChannels(); ++ch) + { + auto* dryPtr = dryBuffer.getWritePointer (ch); + dryDelay[ch].setLengthMs (1000.0f * calcLatencySamples() / (float) getSampleRate()); + + for (int n = 0; n < dryBuffer.getNumSamples(); ++n) + dryPtr[n] = dryDelay[ch].delay (dryPtr[n]); + } + dryWet.processBlock (dryBuffer, buffer); outGain.processBlock (buffer, midiMessages); diff --git a/Plugin/Source/PluginProcessor.h b/Plugin/Source/PluginProcessor.h @@ -18,6 +18,7 @@ #include "Processors/Degrade/DegradeProcessor.h" #include "Processors/Chew/ChewProcessor.h" #include "Processors/DryWetProcessor.h" +#include "Processors/Timing_Effects/DelayProcessor.h" #include "Presets/PresetManager.h" //============================================================================== @@ -51,6 +52,7 @@ public: bool producesMidi() const override; bool isMidiEffect() const override; double getTailLengthSeconds() const override; + float calcLatencySamples() const noexcept; //============================================================================== int getNumPrograms() override; @@ -76,6 +78,7 @@ private: std::unique_ptr<LossFilter> lossFilter[2]; Flutter flutter; DryWetProcessor dryWet; + DelayProcessor dryDelay[2]; GainProcessor outGain; AudioBuffer<float> dryBuffer; diff --git a/Plugin/Source/Processors/Hysteresis/HysteresisProcessor.cpp b/Plugin/Source/Processors/Hysteresis/HysteresisProcessor.cpp @@ -117,6 +117,12 @@ void HysteresisProcessor::releaseResources() overSample[i]->reset(); } +float HysteresisProcessor::getLatencySamples() const noexcept +{ + // latency of oversampling + 1/2 sample latency from Runge-Kutta + return overSample[(int) *osParam]->getLatencyInSamples() + 0.5f; +} + void HysteresisProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& /*midi*/) { setDrive (*driveParam); diff --git a/Plugin/Source/Processors/Hysteresis/HysteresisProcessor.h b/Plugin/Source/Processors/Hysteresis/HysteresisProcessor.h @@ -23,6 +23,8 @@ public: static void createParameterLayout (std::vector<std::unique_ptr<RangedAudioParameter>>& params); + float getLatencySamples() const noexcept; + void setDrive (float newDrive); void setSaturation (float newSat); void setWidth (float newWidth); diff --git a/Plugin/Source/Processors/Timing_Effects/DelayProcessor.cpp b/Plugin/Source/Processors/Timing_Effects/DelayProcessor.cpp @@ -1,12 +1,5 @@ #include "DelayProcessor.h" -void DelayProcessor::setReadPtr (int maxLength) -{ - readPtr++; - if (readPtr >= maxLength) //wrap - readPtr = 0; -} - void DelayProcessor::setLengthMs (double lengthMs, bool force) { auto newLength = lengthMs * sampleRate / 1000.0; @@ -49,35 +42,3 @@ void DelayProcessor::process (float* buffer, int numSamples) for (int n = 0; n < numSamples; n++) buffer[n] += delay (buffer[n]); } - -float DelayProcessor::delay (float x) -{ - // Erase head - const int erasePtr = negativeAwareModulo (readPtr - 1, bufferSize); - delayBuffer.setSample (0, erasePtr, 0.0f); - - // Write head - const float len = length.getNextValue(); - - float y = x; - if (len > 0) - { - // Read head - y = delayBuffer.getSample (0, readPtr); - - // write ptr - const float fractionSample = len - (int) len; - const int writePtr = (readPtr + (int) floorf (len)) % bufferSize; - - // feedback - float writeSample = x + y * feedback; - - delayBuffer.addSample (0, writePtr, writeSample * (1.0f - fractionSample)); - delayBuffer.addSample (0, (writePtr + 1) % bufferSize, writeSample * fractionSample); - } - - //update pointers - setReadPtr (bufferSize); - - return y; -} diff --git a/Plugin/Source/Processors/Timing_Effects/DelayProcessor.h b/Plugin/Source/Processors/Timing_Effects/DelayProcessor.h @@ -11,7 +11,38 @@ public: void prepareToPlay (double sampleRate, int maxExpectedBlockSize); void process (float* buffer, int numSamples); - float delay (float x); + + inline float delay (float x) + { + // Erase head + const int erasePtr = negativeAwareModulo (readPtr - 1, bufferSize); + delayBuffer.setSample (0, erasePtr, 0.0f); + + // Write head + const float len = length.getNextValue(); + + float y = x; + if (len > 0) + { + // Read head + y = delayBuffer.getSample (0, readPtr); + + // write ptr + const float fractionSample = len - (int) len; + const int writePtr = (readPtr + (int) floorf (len)) % bufferSize; + + // feedback + float writeSample = x + y * feedback; + + delayBuffer.addSample (0, writePtr, writeSample * (1.0f - fractionSample)); + delayBuffer.addSample (0, (writePtr + 1) % bufferSize, writeSample * fractionSample); + } + + //update pointers + setReadPtr (bufferSize); + + return y; + } void setLengthMs (double lengthMs, bool force = false); float getLengthMS() const; @@ -29,8 +60,13 @@ private: float feedback = 0.0f; int readPtr = 0; - void resetPtrs() { readPtr = 0; } - void setReadPtr (int maxLength); + inline void resetPtrs() { readPtr = 0; } + inline void setReadPtr (int maxLength) + { + readPtr++; + if (readPtr >= maxLength) //wrap + readPtr = 0; + } float sampleRate = 48000.0f;