AnalogTapeModel

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

commit 76ac813f6f56cbc2acce5f143cf4afd6138c536c
parent 7d7eaab5f1d96293731bcb41a628945b2ea053d4
Author: jatinchowdhury18 <[email protected]>
Date:   Fri, 14 Aug 2020 11:43:31 -0700

Add benchmarking console app (#68)

* Set up benchmarking console app

* Adjust benchmarking app for Linux

* Unroll Newton-Raphson loop

Co-authored-by: jatinchowdhury18 <[email protected]>
Diffstat:
APlugin/Bench/CHOWTapeBench.jucer | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
APlugin/Bench/Source/Main.cpp | 193+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MPlugin/Source/Processors/Hysteresis/HysteresisProcessing.cpp | 34++++++++++++++++++++++++++++++----
3 files changed, 302 insertions(+), 4 deletions(-)

diff --git a/Plugin/Bench/CHOWTapeBench.jucer b/Plugin/Bench/CHOWTapeBench.jucer @@ -0,0 +1,79 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<JUCERPROJECT id="k9sHIZ" name="CHOWTapeBench" projectType="consoleapp" useAppConfig="0" + addUsingNamespaceToJuceHeader="0" jucerFormatVersion="1"> + <MAINGROUP id="KOQkEi" name="CHOWTapeBench"> + <GROUP id="{C7023DBC-3FC3-D40D-C17D-322846554284}" name="Source"> + <FILE id="HsSjYU" name="Main.cpp" compile="1" resource="0" file="Source/Main.cpp"/> + </GROUP> + </MAINGROUP> + <JUCEOPTIONS JUCE_STRICT_REFCOUNTEDPOINTER="1" JUCE_PLUGINHOST_VST3="1"/> + <EXPORTFORMATS> + <LINUX_MAKE targetFolder="Builds/LinuxMakefile"> + <CONFIGURATIONS> + <CONFIGURATION isDebug="1" name="Debug" targetName="CHOWTapeBench"/> + <CONFIGURATION isDebug="0" name="Release" targetName="CHOWTapeBench"/> + </CONFIGURATIONS> + <MODULEPATHS> + <MODULEPATH id="juce_audio_processors" path="../Juce/modules"/> + <MODULEPATH id="juce_events" path="../Juce/modules"/> + <MODULEPATH id="juce_core" path="../Juce/modules"/> + <MODULEPATH id="juce_data_structures" path="../Juce/modules"/> + <MODULEPATH id="juce_gui_extra" path="../Juce/modules"/> + <MODULEPATH id="juce_gui_basics" path="../Juce/modules"/> + <MODULEPATH id="juce_graphics" path="../Juce/modules"/> + <MODULEPATH id="juce_audio_basics" path="../Juce/modules"/> + <MODULEPATH id="juce_audio_formats" path="../Juce/modules"/> + </MODULEPATHS> + </LINUX_MAKE> + <VS2017 targetFolder="Builds/VisualStudio2017"> + <CONFIGURATIONS> + <CONFIGURATION isDebug="1" name="Debug" targetName="CHOWTapeBench"/> + <CONFIGURATION isDebug="0" name="Release" targetName="CHOWTapeBench"/> + </CONFIGURATIONS> + <MODULEPATHS> + <MODULEPATH id="juce_audio_processors" path="../Juce/modules"/> + <MODULEPATH id="juce_events" path="../Juce/modules"/> + <MODULEPATH id="juce_core" path="../Juce/modules"/> + <MODULEPATH id="juce_data_structures" path="../Juce/modules"/> + <MODULEPATH id="juce_gui_extra" path="../Juce/modules"/> + <MODULEPATH id="juce_gui_basics" path="../Juce/modules"/> + <MODULEPATH id="juce_graphics" path="../Juce/modules"/> + <MODULEPATH id="juce_audio_basics" path="../Juce/modules"/> + <MODULEPATH id="juce_audio_formats" path="../Juce/modules"/> + </MODULEPATHS> + </VS2017> + <XCODE_MAC targetFolder="Builds/MacOSX"> + <CONFIGURATIONS> + <CONFIGURATION isDebug="1" name="Debug" targetName="CHOWTapeBench"/> + <CONFIGURATION isDebug="0" name="Release" targetName="CHOWTapeBench"/> + </CONFIGURATIONS> + <MODULEPATHS> + <MODULEPATH id="juce_audio_processors" path="../Juce/modules"/> + <MODULEPATH id="juce_events" path="../Juce/modules"/> + <MODULEPATH id="juce_core" path="../Juce/modules"/> + <MODULEPATH id="juce_data_structures" path="../Juce/modules"/> + <MODULEPATH id="juce_gui_extra" path="../Juce/modules"/> + <MODULEPATH id="juce_gui_basics" path="../Juce/modules"/> + <MODULEPATH id="juce_graphics" path="../Juce/modules"/> + <MODULEPATH id="juce_audio_basics" path="../Juce/modules"/> + <MODULEPATH id="juce_audio_formats" path="../Juce/modules"/> + </MODULEPATHS> + </XCODE_MAC> + </EXPORTFORMATS> + <MODULES> + <MODULE id="juce_audio_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="0"/> + <MODULE id="juce_audio_formats" showAllCode="1" useLocalCopy="0" useGlobalPath="0"/> + <MODULE id="juce_audio_processors" showAllCode="1" useLocalCopy="0" useGlobalPath="0"/> + <MODULE id="juce_core" showAllCode="1" useLocalCopy="0" useGlobalPath="0"/> + <MODULE id="juce_data_structures" showAllCode="1" useLocalCopy="0" useGlobalPath="0"/> + <MODULE id="juce_events" showAllCode="1" useLocalCopy="0" useGlobalPath="0"/> + <MODULE id="juce_graphics" showAllCode="1" useLocalCopy="0" useGlobalPath="0"/> + <MODULE id="juce_gui_basics" showAllCode="1" useLocalCopy="0" useGlobalPath="0"/> + <MODULE id="juce_gui_extra" showAllCode="1" useLocalCopy="0" useGlobalPath="0"/> + </MODULES> + <LIVE_SETTINGS> + <WINDOWS/> + <LINUX/> + </LIVE_SETTINGS> +</JUCERPROJECT> diff --git a/Plugin/Bench/Source/Main.cpp b/Plugin/Bench/Source/Main.cpp @@ -0,0 +1,193 @@ +#include <JuceHeader.h> +#include <iostream> + +namespace +{ + constexpr double pluginSampleRate = 44100.0; + constexpr int samplesPerBlock = 256; + constexpr int numChannels = 2; +} + +using namespace juce; + +File getRootFolder(); +void getAudioFile(AudioBuffer<float>& buffer); +File getPluginFile(); +std::unique_ptr<AudioPluginInstance> getPlugin (String file); +void createRandomAudioInput (AudioBuffer<float>& buffer, double lengthSeconds); +double timeAudioProcess (AudioPluginInstance* plugin, AudioBuffer<float>& audio, const int blockSize); + +int main (int argc, char* argv[]) +{ + ignoreUnused (argc, argv); + + ScopedJuceInitialiser_GUI scopedJuce; + + std::cout << "Loading plugin..." << std::endl; + auto pluginFile = getPluginFile(); + auto plugin = getPlugin (pluginFile.getFullPathName()); + + if (plugin == nullptr) + return 1; + + std::cout << "Loading audio file..." << std::endl; + AudioBuffer<float> audio; + getAudioFile (audio); + const double audioLength = audio.getNumSamples() / pluginSampleRate; // seconds + // std::cout << "Audio file is " << audioLength << " seconds" << std::endl; + + std::cout << "Setting parameters..." << std::endl; + auto params = plugin->getParameters(); + for (auto param : params) + { + if (param->getName (10) == "Oversampling") + param->setValue (3.0f / 4.0f); // 8x + + if (param->getName (10) == "Mode") + param->setValue (3.0f / 5.0f); // NR5 + } + + // process audio + std::cout << "Processing audio..." << std::endl; + plugin->prepareToPlay (pluginSampleRate, samplesPerBlock); + auto time = timeAudioProcess (plugin.get(), audio, samplesPerBlock); + plugin->releaseResources(); + + // print results + std::cout << "Results:" << std::endl; + std::cout << audioLength / time << "x real-time" << std::endl; + std::cout << time << std::endl; + + return 0; +} + +std::unique_ptr<AudioPluginInstance> getPlugin (String file) +{ + AudioPluginFormatManager pluginManager; + pluginManager.addDefaultFormats(); + + OwnedArray<PluginDescription> plugins; + KnownPluginList pluginList; + + // attempt to load plugin from file + File pluginFile; + if (! File::isAbsolutePath (String(file))) + pluginFile = File (File::getCurrentWorkingDirectory().getFullPathName() + "/" + file); + else + pluginFile = File (file); + pluginList.scanAndAddDragAndDroppedFiles (pluginManager, StringArray (pluginFile.getFullPathName()), plugins); + + if (plugins.isEmpty()) // check if loaded + { + std::cout << "Error: unable to load plugin" << std::endl; + return {}; + } + + // create plugin instance + String error ("Unable to load plugin"); + auto plugin = pluginManager.createPluginInstance (*plugins.getFirst(), 44100.0, 256, error); + + return std::move (plugin); +} + +void createRandomAudioInput (AudioBuffer<float>& buffer, double lengthSeconds) +{ + const int numSamples = int (lengthSeconds * pluginSampleRate); + buffer.setSize (numChannels, numSamples); + + Random rand; + for (int ch = 0; ch < numChannels; ++ch) + { + auto* x = buffer.getWritePointer (ch); + for (int n = 0; n < numSamples; ++n) + { + x[n] = rand.nextFloat() * 2.0f - 1.0f; + } + } +} + +double timeAudioProcess (AudioPluginInstance* plugin, AudioBuffer<float>& audio, const int blockSize) +{ + Time time; + + auto totalNumSamples = audio.getNumSamples(); + int samplePtr = 0; + MidiBuffer midi; + + auto start = time.getMillisecondCounterHiRes(); + while (totalNumSamples > 0) + { + auto curBlockSize = jmin (totalNumSamples, blockSize); + totalNumSamples -= curBlockSize; + + AudioBuffer<float> curBuff (audio.getArrayOfWritePointers(), numChannels, samplePtr, curBlockSize); + plugin->processBlock (curBuff, midi); + + samplePtr += curBlockSize; + } + + return (time.getMillisecondCounterHiRes() - start) / 1000.0; +} + +File getPluginFile() +{ + File rootFolder = getRootFolder(); + +#ifdef JUCE_WINDOWS + File pluginFile = rootFolder.getChildFile ("Builds/VisualStudio2017/x64/Release/VST3/CHOWTapeModel.vst3"); +#endif + +#ifdef JUCE_MAC + File pluginFile = rootFolder.getChildFile ("Builds/MacOSX/build/Release/CHOWTapeModel.vst3"); +#endif + +#ifdef JUCE_LINUX + File pluginFile = rootFolder.getChildFile ("Builds/LinuxMakefile/build/CHOWTapeModel.vst3"); +#endif + + // std::cout << "Plugin File: " << pluginFile.getFullPathName() << std::endl; + + jassert (pluginFile.exists()); + + return pluginFile; +} + +File getRootFolder() +{ + File appFile = File::getSpecialLocation (File::currentApplicationFile); + +#ifdef JUCE_WINDOWS + int numParentsToRoot = 7; +#endif + +#ifdef JUCE_MAC + int numParentsToRoot = 6; +#endif + +#ifdef JUCE_LINUX + int numParentsToRoot = 5; +#endif + + File rootFolder = File (appFile); + for (int i = 0; i < numParentsToRoot; ++i) + rootFolder = File (rootFolder.getParentDirectory()); + + // std::cout << "Root Folder: " << rootFolder.getFullPathName() << std::endl; + + return rootFolder; +} + +void getAudioFile(AudioBuffer<float>& buffer) +{ + File rootFolder = getRootFolder(); + File audioFile = rootFolder.getParentDirectory().getChildFile ("Testing/Canada_Dry.wav"); + + AudioFormatManager formatManager; + formatManager.registerBasicFormats(); + + std::unique_ptr<InputStream> inputStream = audioFile.createInputStream(); + std::unique_ptr<AudioFormatReader> reader (formatManager.createReaderFor (std::move (inputStream))); + + buffer.setSize (reader->numChannels, (int) reader->lengthInSamples); + reader->read (buffer.getArrayOfWritePointers(), buffer.getNumChannels(), 0, buffer.getNumSamples()); +} diff --git a/Plugin/Source/Processors/Hysteresis/HysteresisProcessing.cpp b/Plugin/Source/Processors/Hysteresis/HysteresisProcessing.cpp @@ -168,12 +168,38 @@ inline double HysteresisProcessing::NR (double H, double H_d) noexcept { double M = M_n1; const double last_dMdt = hysteresisFunc (M_n1, H_n1, H_d_n1); - for (int n = 0; n < numIter; ++n) + + double dMdt, dMdtPrime, deltaNR; + for (int n = 0; n < numIter; n += 5) { - const double dMdt = hysteresisFunc (M, H, H_d); - const double dMdtPrime = hysteresisFuncPrime (H_d, dMdt); + // loop #1 + dMdt = hysteresisFunc (M, H, H_d); + dMdtPrime = hysteresisFuncPrime (H_d, dMdt); + deltaNR = (M - M_n1 - Talpha * (dMdt + last_dMdt)) / (1.0 - Talpha * dMdtPrime); + M -= deltaNR; + + // loop #2 + dMdt = hysteresisFunc (M, H, H_d); + dMdtPrime = hysteresisFuncPrime (H_d, dMdt); + deltaNR = (M - M_n1 - Talpha * (dMdt + last_dMdt)) / (1.0 - Talpha * dMdtPrime); + M -= deltaNR; + + // loop #3 + dMdt = hysteresisFunc (M, H, H_d); + dMdtPrime = hysteresisFuncPrime (H_d, dMdt); + deltaNR = (M - M_n1 - Talpha * (dMdt + last_dMdt)) / (1.0 - Talpha * dMdtPrime); + M -= deltaNR; + + // loop #4 + dMdt = hysteresisFunc (M, H, H_d); + dMdtPrime = hysteresisFuncPrime (H_d, dMdt); + deltaNR = (M - M_n1 - Talpha * (dMdt + last_dMdt)) / (1.0 - Talpha * dMdtPrime); + M -= deltaNR; - const double deltaNR = (M - M_n1 - Talpha * (dMdt + last_dMdt)) / (1.0 - Talpha * dMdtPrime); + // loop #5 + dMdt = hysteresisFunc (M, H, H_d); + dMdtPrime = hysteresisFuncPrime (H_d, dMdt); + deltaNR = (M - M_n1 - Talpha * (dMdt + last_dMdt)) / (1.0 - Talpha * dMdtPrime); M -= deltaNR; }