Create internal plugin

Hi everyone,

I’m trying to create an internal plugin that can be used in tracktion engine.

I create a very simple one:

This is JPSamplerFxPlugin.h

#include <JuceHeader.h>
#include <tracktion_engine/tracktion_engine.h>

namespace tracktion {
    inline namespace engine
    {

        //==============================================================================
        class JPSamplerFxPlugin : public tracktion::engine::Plugin
        {
        public:
            //==============================================================================
            JPSamplerFxPlugin(PluginCreationInfo);
            ~JPSamplerFxPlugin() override;


            //==============================================================================
            static const char* getPluginName() { return "JP Sampler FX"; }
            static const char* xmlTypeName; // XML type identifier for the plugin

            //==============================================================================
            // Plugin interface methods
            juce::String getName() const override { return "JPSamplerFx"; }
            juce::String getPluginType() override { return xmlTypeName; }
            juce::String getVendor() override { return "Juicy Pads"; }
            juce::String getShortName(int) override { return "JPSamplerFx"; }
            juce::String getSelectableDescription() override { return "JP Sampler FX"; }

            //==============================================================================
            // Audio processing
            void initialise(const PluginInitialisationInfo&) override;
            void deinitialise() override;
            void applyToBuffer(const tracktion::engine::PluginRenderContext& context) override;

            //==============================================================================
            // Parameter management
            void initialiseFully() override;
            void restorePluginStateFromValueTree(const juce::ValueTree&) override;

            //==============================================================================
            AutomatableParameter::Ptr getGainParam() const;
            void setGainParam(float newGain);

            //==============================================================================
            // Plugin parameters
            AutomatableParameter::Ptr gainParam;
            juce::CachedValue<float> gainValue;

        private:


            //==============================================================================
            JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(JPSamplerFxPlugin)
        };

    }
}

Then I have JPSamplerFxPlugin.cpp

#include "JPSamplerFxPlugin.h"

namespace tracktion {
    inline namespace engine
    {
        //==============================================================================
        JPSamplerFxPlugin::JPSamplerFxPlugin(PluginCreationInfo info)
            : Plugin(info)
        {
            auto um = getUndoManager();

            gainValue.referTo(state, IDs::gain, um, 0.0f);

            // Initialize the gain parameter
            gainParam = addParam("gain", "Gain", { 0.0f, 2.0f },
                [](float value) { return juce::String(value, 2); },
                [](const juce::String& s) { return s.getFloatValue(); });

            gainParam->attachToCurrentValue(gainValue);
        }

        JPSamplerFxPlugin::~JPSamplerFxPlugin()
        {
            notifyListenersOfDeletion();

            gainParam->detachFromCurrentValue();
        }

        //==============================================================================
        const char* JPSamplerFxPlugin::xmlTypeName = "JPSamplerFxPlugin";

        //==============================================================================
        void JPSamplerFxPlugin::initialiseFully()
        {
            // Ensure parameters are fully initialized
            Plugin::initialiseFully();
        }

        //==============================================================================
        void JPSamplerFxPlugin::restorePluginStateFromValueTree(const juce::ValueTree& v)
        {
            copyValueTree(state, v, nullptr);
        }

        //==============================================================================
        void JPSamplerFxPlugin::initialise(const PluginInitialisationInfo&)
        {

        }

        //==============================================================================
        void JPSamplerFxPlugin::deinitialise()
        {

        }

        //==============================================================================
        void JPSamplerFxPlugin::applyToBuffer(const PluginRenderContext& context)
        {
            if (context.destBuffer == nullptr)
                return;

            // Get the current gain value
            float gain = gainParam->getCurrentValue();

            // Apply gain to each channel in the audio buffer
            for (int channel = 0; channel < context.destBuffer->getNumChannels(); ++channel)
            {
                auto* buffer = context.destBuffer->getWritePointer(channel);
                for (int i = 0; i < context.bufferNumSamples; ++i)
                {
                    buffer[i] *= gain;
                }
            }
        }

        //==============================================================================
        AutomatableParameter::Ptr JPSamplerFxPlugin::getGainParam() const
        {
            return gainParam;
        }

        //==============================================================================
        void JPSamplerFxPlugin::setGainParam(float newGain)
        {
            if (gainParam != nullptr)
                gainParam->setParameter(newGain, juce::NotificationType::sendNotification);
        }
    }
}

all this file are inside a folder called “Plugins” inside my “Source” folder.

Then in my App I try this:

In App.h:

tracktion::PluginManager pluginManager;

And in App.cpp I have this:

pluginManager.initialise();
pluginManager.createBuiltInType<tracktion::engine::JPSamplerFxPlugin>();

Then after my edit initialization I have:

juce::ValueTree pluginState(tracktion_engine::IDs::PLUGIN);
    pluginState.setProperty(tracktion_engine::IDs::type, tracktion_engine::JPSamplerFxPlugin::xmlTypeName, nullptr);
    auto jPSamplerFxPlugin = pluginManager.createNewPlugin(*edit, pluginState);

    DBG("Plugin created? " + juce::String(jPSamplerFxPlugin != nullptr ? "true" : "false"));
    if (jPSamplerFxPlugin == nullptr) {
        DBG("ERROR: jPSamplerFxPlugin is NULL after creation attempt. Aborting insertion.");
        return;
    }
    else {
        DBG("Is not null");
    }

    if (jPSamplerFxPlugin != nullptr)
    {
        auto tracks = tracktion::getAudioTracks(*edit);
        if (tracks.size() != 0 && tracks[0] != nullptr)
        {
            tracks[0]->pluginList.insertPlugin(*jPSamplerFxPlugin, 2, nullptr);
            DBG("JPSamplerFxPlugin inserted into track 0");
            bool foundMyPlugin = false;
            for (auto* plugin : tracks[0]->pluginList)
            {
                if (plugin == jPSamplerFxPlugin.get())
                {
                    foundMyPlugin = true;
                    DBG("CONFIRMATION: JPSamplerFxPlugin found in track's pluginList immediately after insertion.");
                    break;
                }
            }
            if (!foundMyPlugin)
            {
                DBG("WARNING: JPSamplerFxPlugin NOT found in track's pluginList immediately after insertion.");
            }

            for (auto* plugin : tracks[0]->pluginList)
            {
                DBG("Searching plugins, found: " + plugin->getName());
                if (auto* jpSamplerFx = dynamic_cast<tracktion::engine::JPSamplerFxPlugin*>(plugin))
                {
                    jpSamplerFx->setGainParam(2.0f);
                    DBG("Parameter: " + juce::String(jpSamplerFx->getGainParam()->getCurrentValueAsString()));
                }
            }
        }
        else
        {
            DBG("No track available");
        }
    }
    else
    {
        DBG("JPSamplerFxPlugin non created");
    }

It compile but on this line:

 tracks[0]->pluginList.insertPlugin(*jPSamplerFxPlugin, -1, nullptr);

I have this error: *** ERROR: Failed to create plugin: JPSamplerFxPlugin

*** ERROR: Failed to create plugin: JPSamplerFxPlugin
JPSamplerFxPlugin inserted into track 0
WARNING: JPSamplerFxPlugin NOT found in track's pluginList immediately after insertion.
Searching plugins, found: Sampler
Searching plugins, found: Pitch Shifter
Searching plugins, found: Volume & Pan Plugin
Searching plugins, found: Level Meter

Who know what I’m missing?

I tried also this:

    auto id = edit->createNewItemID();
    id.writeID(edit->state, nullptr);

    juce::ValueTree pluginState(tracktion_engine::IDs::PLUGIN);
    pluginState.setProperty(tracktion_engine::IDs::type, tracktion_engine::JPSamplerFxPlugin::xmlTypeName, nullptr);

    auto plugin = std::make_shared<tracktion::engine::JPSamplerFxPlugin>(tracktion::engine::PluginCreationInfo(*edit, pluginState, true));
    auto* track = tracktion::getAudioTracks(*edit)[0];
    track->pluginList.insertPlugin(plugin.get(), 0, nullptr);

But here I receive:

*** ERROR: Failed to create plugin: JPSamplerFxPlugin

Thank you very much in advance!

Luca!

Hi,

You should not create a PluginManager instance in App.h - just use the one from the engine:

template<typename TPlugin>
inline static void registerPluginType()
{
    if (engine != nullptr)
        engine->getPluginManager().createBuiltInType<TPlugin>();
}

Then you can create a plugin instance like so:

if (auto t = EngineHelpers::getOrInsertAudioTrackAt (edit, trackIndex))
{
    auto* plugin = edit.getPluginCache()
                       .createNewPlugin (pluginType, {}).get(); // pluginType is the xmlTypeName, so "JPSamplerFxPlugin" in this case.
    if(plugin == nullptr)
    {
        juce::Logger::writeToLog("Failed to create plugin.");
        return false;
    }

    t->pluginList.insertPlugin(plugin, pluginIndex, nullptr);
    return true;
}

I hope this helps.

Hi Bencekovacs, thank you for you kindly reply.

I tried but without a lot of lock.

In my App.h I add this inside public

template<typename TPlugin>
inline void registerPluginType()
{
    engine.getPluginManager().createBuiltInType<TPlugin>();
}

Then in my App.cpp I write this:

App::App(tracktion::Engine& engineRef)
    : engine(engineRef)
{
    App::instance = this;
    registerPluginType<tracktion_engine::JPSamplerFxPlugin>();

Not sure about the registerPluginType<tracktion_engine::JPSamplerFxPlugin>(); line

Then I tried this:

  auto* plugin = edit.getPluginCache().createNewPlugin(tracktion::engine::JPSamplerFxPlugin::xmlTypeName, {}).get();
  if (plugin == nullptr)
  {
      DBG("Failed to create plugin.");
  }

  tracks[0]->pluginList.insertPlugin(plugin, -1, nullptr);

But I have this error:

'==': no conversion from 'nullptr' to '<error type>'
'getPluginCache': is not a member of 'std::unique_ptr<tracktion::engine::Edit,std::default_delete<tracktion::engine::Edit>>'
'plugin': cannot be used before it is initialized

Edit in my App.h is defined like that:

std::unique_ptr<tracktion::Edit> edit;

and in my App.cpp I have this before plugin init:

edit = tracktion::engine::loadEditFromFile(engine, tempEditFile);

I tried to change the line:

    auto* plugin = edit.getPluginCache().createNewPlugin(tracktion::engine::JPSamplerFxPlugin::xmlTypeName, {}).get();

with:

    auto* plugin = edit->getPluginCache().createNewPlugin(tracktion::engine::JPSamplerFxPlugin::xmlTypeName, {}).get();

But then I have this:

JUCE Assertion failure in juce_ReferenceCountedObject.h:406
A breakpoint instruction (__debugbreak() statement or a similar call) was executed in JuicyPadsSampler.exe.

Thank you for your help!

Luca

With this:

    auto& pluginManager = engine.getPluginManager();
    registerPluginType<tracktion_engine::JPSamplerFxPlugin>();

    edit->ensureNumberOfAudioTracks(1);
    auto tracks = tracktion::getAudioTracks(*edit);
    if (tracks.isEmpty())
    {
        DBG("Errore: nessuna traccia audio disponibile");
        jassertfalse;
        return;
    }

    DBG("Creation of JPSamplerFxPlugin plugin");
    DBG("Attempting to create plugin with xmlTypeName: " + juce::String(tracktion::engine::JPSamplerFxPlugin::xmlTypeName));
    auto plugin = edit->getPluginCache().createNewPlugin(tracktion::engine::JPSamplerFxPlugin::xmlTypeName, {});
    if (plugin == nullptr)
    {
        jassertfalse;
        return;
    }

    DBG("Plugin created successfully: " + plugin->getName());
    tracks[0]->pluginList.insertPlugin(std::move(plugin), -1, nullptr);
    DBG("-- PLUGINS LIST FOR TRACK 1");
    for (auto* plugin : tracks[0]->pluginList)
    {
        DBG("Searching plugins, found: " + plugin->getName());
    }

I have this:

-- PLUGINS LIST FOR TRACK 1
Searching plugins, found: Sampler
Searching plugins, found: Sampler
Searching plugins, found: Pitch Shifter
Searching plugins, found: Volume & Pan Plugin
Searching plugins, found: Level Meter
Searching plugins, found: JPSamplerFx

But then it crashes :frowning:

Maybe something inside plugin file?

I attach the two files!

JPSamplerFxPlugin.h (2.7 KB)

JPSamplerFxPlugin.cpp (2.9 KB)

If you have a crash you need to provide the stack trace so we can see where it happens.

You are right!

I can manage to start the plugin with the code of bencekovacs

Thanks!

1 Like

Hi all, I have another question.

If I save the edit with this plugins then I should be able to reopen it?
Because if I open with Waveform I don’t see my custom plugin (make sense…)

Thanks
Luca

Yes, that’s correct. If it’s your own internal Plugin subclass then no, Waveform won’t be able to open it.

But if I open with my software based on tracktion_engine should be ok? Or I need to reinsert manually the plugins?

No, it should load exactly as it was.

1 Like

Thx dave96,

just another question about the strategy to create/save edit.

My idea is:

  • create a temp.tracktionedit with Waveform and save and use inside my project
  • when I save a create a copy of the edit

But not sure about for example Sampler Plugin if it will lost the path of the file (that should be the new folder).

Maybe is better to create a tracktionedit file directly inside my software?

What do you suggest?

Thanks

It really depends on what it contains. If you add Waveform specific plugins etc. then it won’t open in just the Tracktion Engine fully.

The other thing is that Waveform uses ProjectItemID for file references.

It much simpler for TE Edits to just use relative paths. We only do this in Waveform for legacy reasons.

So you suggest to create an edit “template” on the fly when I start the program and the save it?

When I create it with waveform I have a lot of folder, they are not needed if I create in in TE on the fly?

Thanks!

What are you actually trying to do?

Create a temp project in order to work directly when I open my software, then if I save is good if not I will lose my work :smiley:

Something like that seams to work:

  auto folder = ProjectManager::getTempProjectFolder();
  ProjectManager::getProjectSoundsFolder("temp").createDirectory();
  ProjectManager::getProjectExportsFolder("temp").createDirectory();
  DBG("Temp edit: " + ProjectManager::getTempEditFile().getFullPathName());
  edit = tracktion::engine::createEmptyEdit(engine, ProjectManager::getTempEditFile());

  if (edit == nullptr)
  {
      DBG("Error: impossible to load an edit");
      jassertfalse;
      return;
  }
  else {
      DBG("Edit loaded successfully!");
  }

  edit->tempoSequence.getTempos()[0]->setBpm(settingsManager->getValue("default_project_bpm", 90));
  edit->setClickTrackVolume(1.0f);
  edit->clickTrackEnabled = false;
  edit->clickTrackEmphasiseBars = false;
  edit->ensureNumberOfAudioTracks(8);
  auto& pluginManager = engine.getPluginManager();

  registerPluginType<tracktion_engine::JPSamplerFxPlugin>();
  //auto jPSamplerFxPlugin = edit->getPluginCache().createNewPlugin(tracktion::engine::JPSamplerFxPlugin::xmlTypeName, {});

  registerPluginType<tracktion_engine::JPSamplerPlugin>();
  //auto jPSamplerPlugin = edit->getPluginCache().createNewPlugin(tracktion::engine::JPSamplerPlugin::xmlTypeName, {});

  auto tracks = tracktion::getAudioTracks(*edit);

  if (tracks.isEmpty())
  {
      DBG("Error: no tracks available");
      jassertfalse;
      return;
  }

  for (int i = 0; i < tracks.size(); ++i)
  {
      auto* track = tracks[i];

      if (track->pluginList.size() > 0 && i == 0)
          track->pluginList[0]->deleteFromParent();

      if (i == 0)
      {
          auto jPSamplerPlugin1 = edit->getPluginCache().createNewPlugin(tracktion::JPSamplerPlugin::xmlTypeName, {});
          if (jPSamplerPlugin1 != nullptr)
              track->pluginList.insertPlugin(std::move(jPSamplerPlugin1), 0, nullptr);

          auto jPSamplerPlugin2 = edit->getPluginCache().createNewPlugin(tracktion::JPSamplerPlugin::xmlTypeName, {});
          if (jPSamplerPlugin2 != nullptr)
          {
              track->pluginList.insertPlugin(std::move(jPSamplerPlugin2), 1, nullptr);
              jPSamplerPlugin2->setEnabled(false);
          }
      }
      else {
          auto samplerPlugin1 = edit->getPluginCache().createNewPlugin(tracktion::SamplerPlugin::xmlTypeName, {});
          if (samplerPlugin1 != nullptr)
              track->pluginList.insertPlugin(std::move(samplerPlugin1), 0, nullptr);
          auto samplerPlugin2 = edit->getPluginCache().createNewPlugin(tracktion::SamplerPlugin::xmlTypeName, {});
          if (samplerPlugin2 != nullptr)
          {
              track->pluginList.insertPlugin(std::move(samplerPlugin2), 1, nullptr);
              samplerPlugin2->setEnabled(false);
          }
      }

      auto pitchShiftPlugin = edit->getPluginCache().createNewPlugin(tracktion::PitchShiftPlugin::xmlTypeName, {});
      if (pitchShiftPlugin != nullptr)
          track->pluginList.insertPlugin(std::move(pitchShiftPlugin), 2, nullptr);

      auto fxPlugin = edit->getPluginCache().createNewPlugin(tracktion::JPSamplerFxPlugin::xmlTypeName, {});
      if (fxPlugin != nullptr)
      {
          track->pluginList.insertPlugin(std::move(fxPlugin), 3, nullptr);
          fxPlugin->setEnabled(false);
      }

      // Log
      DBG("-- PLUGINS LIST FOR TRACK " + juce::String(i));
      for (auto* p : track->pluginList)
          DBG("   - " + p->getName() + " " + (p->isEnabled() ? "Enabled" : "Disabled"));
  }
  tracktion::engine::EditFileOperations editOps(*edit);
  if (editOps.writeToFile(ProjectManager::getTempEditFile(), false))
      DBG("Salving completed");
  else
      DBG("Error on saving");

and here: ProjectManager::getTempProjectFolder(); I delete the whole directory so I start with new project every time I open my software

Thanks!