AnalogTapeModel

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

PluginProcessor.cpp (13152B)


      1 /*
      2   ==============================================================================
      3 
      4     This file was auto-generated!
      5 
      6     It contains the basic framework code for a JUCE plugin processor.
      7 
      8   ==============================================================================
      9 */
     10 
     11 #include "PluginProcessor.h"
     12 #include "GUI/ModulatableSlider.h"
     13 #include "GUI/OnOff/PowerButton.h"
     14 #include "GUI/OversamplingMenu.h"
     15 #include "GUI/SettingsButton.h"
     16 #include "GUI/TitleComp.h"
     17 #include "GUI/TooltipComp.h"
     18 #include "GUI/Visualizers/MixGroupViz.h"
     19 #include "GUI/WowFlutterMenu.h"
     20 #include "Presets/PresetManager.h"
     21 
     22 #if JUCE_IOS
     23 #include "GUI/IOSOnly/ScrollView.h"
     24 #include "GUI/IOSOnly/TipJar.h"
     25 #endif
     26 
     27 namespace
     28 {
     29 const String settingsFilePath = "ChowdhuryDSP/ChowTape/.plugin_settings.json";
     30 
     31 const String isStereoTag = "plugin:is_stereo";
     32 
     33 const String inGainTag = "ingain";
     34 const String outGainTag = "outgain";
     35 const String dryWetTag = "drywet";
     36 } // namespace
     37 
     38 //==============================================================================
     39 ChowtapeModelAudioProcessor::ChowtapeModelAudioProcessor() : inputFilters (vts),
     40                                                              midSideController (vts),
     41                                                              toneControl (vts),
     42                                                              compressionProcessor (vts),
     43                                                              hysteresis (vts),
     44                                                              degrade (vts),
     45                                                              chewer (vts),
     46                                                              lossFilter (vts),
     47                                                              flutter (vts),
     48                                                              onOffManager (vts, this),
     49                                                              mixGroupsController (vts, this)
     50 {
     51     pluginSettings->initialise (settingsFilePath);
     52 
     53     chowdsp::ParamUtils::loadParameterPointer (inGainDBParam, vts, inGainTag);
     54     chowdsp::ParamUtils::loadParameterPointer (outGainDBParam, vts, outGainTag);
     55     chowdsp::ParamUtils::loadParameterPointer (dryWetParam, vts, dryWetTag);
     56 
     57     presetManager = std::make_unique<PresetManager> (vts);
     58 
     59     positionInfo.bpm = 120.0;
     60     positionInfo.timeSigNumerator = 4;
     61 
     62     scope = magicState.createAndAddObject<TapeScope> ("scope");
     63     flutter.initialisePlots (magicState);
     64 
     65     LookAndFeel::setDefaultLookAndFeel (&myLNF);
     66 
     67     PluginHostType hostType;
     68     if (hostType.isRenoise()) // Renoise has different gain staging, so we handle that here
     69         toneControl.setDBScale (12.0f);
     70     else
     71         toneControl.setDBScale (18.0f);
     72 }
     73 
     74 void ChowtapeModelAudioProcessor::addParameters (Parameters& params)
     75 {
     76     using namespace chowdsp::ParamUtils;
     77     createGainDBParameter (params, inGainTag, "Input Gain", -30.0f, 6.0f, 0.0f);
     78     createGainDBParameter (params, outGainTag, "Output Gain", -30.0f, 30.0f, 0.0f);
     79     createPercentParameter (params, dryWetTag, "Dry/Wet", 1.0f);
     80 
     81     InputFilters::createParameterLayout (params);
     82     ToneControl::createParameterLayout (params);
     83     CompressionProcessor::createParameterLayout (params);
     84     HysteresisProcessor::createParameterLayout (params);
     85     LossFilter::createParameterLayout (params);
     86     WowFlutterProcessor::createParameterLayout (params);
     87     DegradeProcessor::createParameterLayout (params);
     88     ChewProcessor::createParameterLayout (params);
     89     MidSideProcessor::createParameterLayout (params);
     90     MixGroupsController::createParameterLayout (params);
     91 }
     92 
     93 void ChowtapeModelAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock)
     94 {
     95     const auto numChannels = getTotalNumInputChannels();
     96     setRateAndBufferSizeDetails (sampleRate, samplesPerBlock);
     97 
     98     inGain.prepareToPlay (sampleRate, samplesPerBlock);
     99     inputFilters.prepareToPlay (sampleRate, samplesPerBlock, numChannels);
    100     midSideController.prepare (sampleRate, samplesPerBlock);
    101     toneControl.prepare (sampleRate, numChannels);
    102     compressionProcessor.prepare (sampleRate, samplesPerBlock, numChannels);
    103     hysteresis.prepareToPlay (sampleRate, samplesPerBlock, numChannels);
    104     degrade.prepareToPlay (sampleRate, samplesPerBlock, numChannels);
    105     chewer.prepare (sampleRate, samplesPerBlock, numChannels);
    106     lossFilter.prepare ((float) sampleRate, samplesPerBlock, numChannels);
    107 
    108     dryDelay.prepare ({ sampleRate, (uint32) samplesPerBlock, (uint32) numChannels });
    109     dryDelay.setDelay (calcLatencySamples());
    110 
    111     flutter.prepareToPlay (sampleRate, samplesPerBlock, numChannels);
    112     outGain.prepareToPlay (sampleRate, samplesPerBlock);
    113 
    114     scope->setNumChannels (numChannels);
    115     scope->prepareToPlay (sampleRate, samplesPerBlock);
    116 
    117     dryWet.setDryWet (*vts.getRawParameterValue ("drywet") / 100.0f);
    118     dryWet.reset();
    119     dryBuffer.setSize (numChannels, samplesPerBlock);
    120 
    121     setLatencySamples (roundToInt (calcLatencySamples()));
    122     magicState.getPropertyAsValue (isStereoTag).setValue (numChannels == 2);
    123 }
    124 
    125 void ChowtapeModelAudioProcessor::releaseResources()
    126 {
    127     hysteresis.releaseResources();
    128 }
    129 
    130 float ChowtapeModelAudioProcessor::calcLatencySamples() const noexcept
    131 {
    132     return lossFilter.getLatencySamples() + hysteresis.getLatencySamples() + compressionProcessor.getLatencySamples();
    133 }
    134 
    135 bool ChowtapeModelAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const
    136 {
    137     if (wrapperType == juce::AudioProcessor::WrapperType::wrapperType_LV2)
    138     {
    139         return ((layouts.getMainOutputChannelSet() == AudioChannelSet::stereo())
    140                 && (layouts.getMainInputChannelSet() == AudioChannelSet::stereo()));
    141     }
    142     else
    143     {
    144         return ((! layouts.getMainInputChannelSet().isDiscreteLayout())
    145                 && (! layouts.getMainOutputChannelSet().isDiscreteLayout())
    146                 && (layouts.getMainInputChannelSet() == layouts.getMainOutputChannelSet())
    147                 && (! layouts.getMainInputChannelSet().isDisabled()));
    148     }
    149 }
    150 
    151 void ChowtapeModelAudioProcessor::processBlockBypassed (AudioBuffer<float>& buffer, MidiBuffer&)
    152 {
    153     ScopedNoDenormals noDenormals;
    154 
    155     dryBuffer.makeCopyOf (buffer, true);
    156     latencyCompensation();
    157     buffer.makeCopyOf (dryBuffer, true);
    158 }
    159 
    160 void ChowtapeModelAudioProcessor::processAudioBlock (AudioBuffer<float>& buffer)
    161 {
    162     if (auto* playhead = getPlayHead())
    163         playhead->getCurrentPosition (positionInfo);
    164 
    165     inGain.setGain (Decibels::decibelsToGain (inGainDBParam->getCurrentValue()));
    166     outGain.setGain (Decibels::decibelsToGain (outGainDBParam->getCurrentValue()));
    167     dryWet.setDryWet (dryWetParam->getCurrentValue());
    168 
    169     dryBuffer.makeCopyOf (buffer, true);
    170     inGain.processBlock (buffer);
    171     inputFilters.processBlock (buffer);
    172 
    173     scope->pushSamplesIO (buffer, TapeScope::AudioType::Input);
    174 
    175     midSideController.processInput (buffer);
    176     toneControl.processBlockIn (buffer);
    177     compressionProcessor.processBlock (buffer);
    178     hysteresis.processBlock (buffer);
    179     toneControl.processBlockOut (buffer);
    180     chewer.processBlock (buffer);
    181     degrade.processBlock (buffer);
    182     flutter.processBlock (buffer);
    183     lossFilter.processBlock (buffer);
    184 
    185     latencyCompensation();
    186 
    187     midSideController.processOutput (buffer);
    188     inputFilters.processBlockMakeup (buffer);
    189     outGain.processBlock (buffer);
    190     dryWet.processBlock (dryBuffer, buffer);
    191 
    192     chowdsp::BufferMath::sanitizeBuffer (buffer);
    193 
    194     scope->pushSamplesIO (buffer, TapeScope::AudioType::Output);
    195 }
    196 
    197 void ChowtapeModelAudioProcessor::latencyCompensation()
    198 {
    199     // delay dry buffer to avoid phase issues
    200     const auto latencySampFloat = calcLatencySamples();
    201     const auto latencySamp = roundToInt (latencySampFloat);
    202     setLatencySamples (latencySamp);
    203 
    204     // delay makeup block from input filters
    205     inputFilters.setMakeupDelay (latencySampFloat);
    206 
    207     // For "true bypass" use integer sample delay to avoid delay
    208     // line interpolation freq. response issues
    209     if (dryWet.getDryWet() < 0.15f)
    210     {
    211         dryDelay.setDelay ((float) latencySamp);
    212     }
    213     else
    214     {
    215         dryDelay.setDelay (latencySampFloat);
    216     }
    217 
    218     dsp::AudioBlock<float> block { dryBuffer };
    219     dryDelay.process (dsp::ProcessContextReplacing<float> { block });
    220 }
    221 
    222 AudioProcessorEditor* ChowtapeModelAudioProcessor::createEditor()
    223 {
    224     if (openGLHelper == nullptr)
    225         openGLHelper = std::make_unique<chowdsp::OpenGLHelper>();
    226 
    227     auto builder = std::make_unique<foleys::MagicGUIBuilder> (magicState);
    228     builder->registerJUCEFactories();
    229     builder->registerFactory ("presets", &chowdsp::PresetsItem<ChowtapeModelAudioProcessor>::factory);
    230     builder->registerFactory ("TooltipComp", &TooltipItem::factory);
    231     builder->registerFactory ("ModSlider", &ModSliderItem::factory);
    232     builder->registerFactory ("TitleComp", &TitleItem::factory);
    233     builder->registerFactory ("MixGroupViz", &MixGroupVizItem::factory);
    234     builder->registerFactory ("PowerButton", &PowerButtonItem::factory);
    235     builder->registerFactory ("OversamplingMenu", &chowdsp::OversamplingMenuItem<ChowtapeModelAudioProcessor, OversamplingMenu>::factory);
    236     builder->registerFactory ("SettingsButton", &SettingsButtonItem::factory);
    237     builder->registerFactory ("InfoComp", &chowdsp::InfoItem<ChowtapeModelAudioProcessor>::factory);
    238 
    239     builder->registerFactory ("FlutterMenu", [] (foleys::MagicGUIBuilder& b, const ValueTree& node) -> std::unique_ptr<foleys::GuiItem>
    240                               { return std::make_unique<WowFlutterMenuItem> (b, node, "Flutter"); });
    241 
    242     builder->registerFactory ("WowMenu", [] (foleys::MagicGUIBuilder& b, const ValueTree& node) -> std::unique_ptr<foleys::GuiItem>
    243                               { return std::make_unique<WowFlutterMenuItem> (b, node, "Wow"); });
    244 
    245     builder->registerJUCELookAndFeels();
    246     builder->registerLookAndFeel ("MyLNF", std::make_unique<MyLNF>());
    247     builder->registerLookAndFeel ("ComboBoxLNF", std::make_unique<ComboBoxLNF>());
    248     builder->registerLookAndFeel ("PresetsLNF", std::make_unique<PresetsLNF>());
    249     builder->registerLookAndFeel ("SpeedButtonLNF", std::make_unique<SpeedButtonLNF>());
    250 
    251     if (auto* speedHandle = dynamic_cast<AudioParameterFloat*> (vts.getParameter ("speed")))
    252     {
    253         for (auto speed : { 3.75f, 7.5f, 15.0f, 30.0f })
    254         {
    255             magicState.addTrigger ("set_speed_" + String (speed, 2, false), [speedHandle, speed]
    256                                    {
    257                 speedHandle->beginChangeGesture();
    258                 speedHandle->setValueNotifyingHost (speedHandle->convertTo0to1 (speed));
    259                 speedHandle->endChangeGesture(); });
    260         }
    261     }
    262     else
    263     {
    264         // speedHandle was nullptr!
    265         jassertfalse;
    266     }
    267 
    268 #if JUCE_IOS
    269     builder->registerFactory ("ScrollView", &ScrollView::factory);
    270     builder->registerFactory ("TipJar", &TipJarItem::factory);
    271     auto* editor = new foleys::MagicPluginEditor (magicState, BinaryData::gui_ios_xml, BinaryData::gui_ios_xmlSize, std::move (builder));
    272 #else
    273     auto* editor = new foleys::MagicPluginEditor (magicState, BinaryData::gui_xml, BinaryData::gui_xmlSize, std::move (builder));
    274 #endif
    275 
    276     onOffManager.setOnOffForNewEditor (editor);
    277 
    278 #if CHOWDSP_AUTO_UPDATE
    279     updater.showUpdaterScreen (editor);
    280 #endif
    281 
    282     // we need to set resize limits for StandalonePluginHolder
    283     editor->setResizeLimits (10, 10, 2000, 2000);
    284 
    285     openGLHelper->setComponent (editor);
    286 
    287     return editor;
    288 }
    289 
    290 void ChowtapeModelAudioProcessor::getStateInformation (MemoryBlock& destData)
    291 {
    292     auto xml = std::make_unique<XmlElement> ("state");
    293     xml->setAttribute ("version", chowdsp::VersionUtils::Version (JucePlugin_VersionString).getVersionString());
    294 
    295     auto state = vts.copyState();
    296     xml->addChildElement (state.createXml().release());
    297     xml->addChildElement (presetManager->saveXmlState().release());
    298 
    299     copyXmlToBinary (*xml, destData);
    300 }
    301 
    302 void ChowtapeModelAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
    303 {
    304     auto xmlState = getXmlFromBinary (data, sizeInBytes);
    305     if (xmlState == nullptr)
    306     {
    307         // let's check if state was saved with Foley's methods
    308         auto tree = ValueTree::readFromData (data, size_t (sizeInBytes));
    309         if (tree.isValid())
    310             vts.replaceState (tree);
    311 
    312         return;
    313     }
    314 
    315     if (xmlState->hasAttribute ("version"))
    316     {
    317         auto* vtsXml = xmlState->getChildByName (vts.state.getType());
    318         if (vtsXml == nullptr) // invalid ValueTreeState
    319             return;
    320 
    321         presetManager->loadXmlState (xmlState->getChildByName (chowdsp::PresetManager::presetStateTag));
    322         vts.replaceState (ValueTree::fromXml (*vtsXml));
    323     }
    324     else
    325     {
    326         // state was saved before we started tracking the version with the state,
    327         // so let's load state the old way...
    328         if (xmlState->hasTagName (vts.state.getType()))
    329             vts.replaceState (juce::ValueTree::fromXml (*xmlState));
    330     }
    331 }
    332 
    333 // This creates new instances of the plugin..
    334 AudioProcessor* JUCE_CALLTYPE createPluginFilter()
    335 {
    336     return new ChowtapeModelAudioProcessor();
    337 }