lsp-plugins icon indicating copy to clipboard operation
lsp-plugins copied to clipboard

MIDI Utilities Feature Request

Open techdude1996 opened this issue 6 years ago • 5 comments

@sadko4u I've made the email an issue for ease of communication and discussion. I have a mockup of what features would be included in this plugin. This is not how the plugin will be styled, but just what features and GUI widgets for each. midi override mockup

Where do I start this feature?

techdude1996 avatar Sep 28 '18 21:09 techdude1996

First of all, I think, you should describe a concept of plugin. What each control is designed for and how it will affect the MIDI data transformations. First of all, you should create a branch from 'devel' branch to start development. When you finish development, we'll merge your branch into 'devel', and after release it will migrate into 'master'. First of all, you need to understand the architecture of LSP. Here's short information I've sent to @CrocoDuckoDucks in a private email when he started plugin development.

If you have questions we may discuss them in chat mode. First of all you have to understand how do plugins work. I've down a pair of drawings that describe the architecture and main principles.

default Figure 1: LSP Architecture.

You may see here different hosts that utilize different plugin interfaces like LADSPA, LV2, VST, etc. For each plugin interface there is a thin wrapper in LSP core architecture. Currently you have no need to study how do wrappers work (but it would be a good option for deeper understanding). The only thing you should know that all wrappers utilize methods of plugin_t interface. All methods provided by plugin_t interface implement all necessary functionality of plugin. Then, we have inheritance interface: we declare different classes that inherit from plugin_t interface and override methods of this interface. So instead of calling interface methods, thanks to virtual functions, wrappers call methods of derived classes. So we get different plugin modules like comp_delay_mono, comp_delay_stereo, etc. All these modules have common parts that are implemented as separate functional blocks called Core DSP Modules.

Now, we need to study how do wrappers interact with plugin modules. At this point we should understand plugin model and data flows. default

Figure 2: Plugin model. This figure shows the generalized plugin model from the point of view of the Host. Plugin has set of audio inputs, set of audio outputs, it also has input parameters to control the workflow of plugin, and set of output parameters (often called meters) to interact with user and display useful information. The overall data flow between host and plugin consists of similar cycles that can be divided into several periods.

default Figure 3. Plugin lifecycle.

As you see, there's nothing special, all plugins have similar lifecycle, independent from what host is running them and what plugin interface (LV2, VST, etc...) is used. Each plugin has initialization stage, finalization stage and main processing cycle stage. The common plugin interface between wrappers and plugin_t generalizes this behaviour.

Now we can draw the interaction diagram in terms of plugin_t interface: default Figure 4. Plugin interaction diagram. Here you can see what does user do with host and how this reflects on the plugin interface. We see that when user creates a plugin, host-wrapper interface calls set of methods: executes constructor, performs plugin initialization, reports current sample rate to plugin, activates plugin. Then it does regular job at his own cycle, calls main processing method process() and displays output values of plugin (meters) to user. When user tries to change one or more input parameters, host notifies plugin about change of input parameters by calling update_settings() method. When user disables plugin, deactivated() method is called. When user enables plugin again, activated() method is called and host does job as usual. When user deletes plugin, host first deactivates plugin by calling deactivated() method, then destroys plugin instance by calling destroy() method and then calls destructor.

The only question left is: what about parameters and buffers? How are they passed? When I developed the wrapper layer, I decided that the best solution is to incapsulate each parameter, each meter and each audio buffer into separate data unit called 'port'. That allows to solve problems that different plugin formats have different parameter and audio data passing mechanisms. So there are only 3 basic methods to pass/retrieve data from the host to plugin and back:

  1. getValue() - this method is called by plugin when it wants to retrieve current value of parameter from host.
  2. setValue() - this method is called by plugin when it wants to pass current value of meter to host.
  3. getBuffer() - this method is called by plugin when it wants to retrieve audio buffer or another complex data from host. The only rule is: plugin should know what data type is stored in port.

So, in reality we can draw the process() method more detailed: default Figure 5. The process() method.

We can see that, when calling process(), wrapper passes number of samples that will contain all audio buffers. Plugin can read values from ports that are stored in vPorts container (this is protected member of plugin_t class). All data operations are performed by using getValue(), getBuffer() and setValue() methods. Also wrapper does some cool job with input data: it detects if the state of input parameters has changed and triggers update_settings() method when at least one of parameters has changed, so we have no need to read parameters on each iteration of process(). This we can do once in update_settings() method.

sadko4u avatar Sep 28 '18 21:09 sadko4u

So now we know how does plugin work but... How do we describe ports? Simple, by providing metadata for each plugin. Metadata contains complete description of all ports used by plugin and their types. When wrapper instantiates plugin, it reads plugin's metadata and creates corresponding ports for parameter and data transport. So when init() method is called, plugin has already vPorts member collection filled with complete list of ports.

Now let's practice. For example, we want to implement a stereo plugin that adjusts gain. First, we describe list of port:

    static const port_t test_ports[] =
    {
        PORTS_STEREO_PLUGIN,
        AMP_GAIN10("gain", "Output gain", 1.0f),

        PORTS_END
    };

This may look strange, but most of this are macros that are expanded to:

    { PORT_NAME_INPUT_L, "Input L", U_NONE, R_AUDIO, F_IN, 0, 0, 0, 0, 0, 0    },
    { PORT_NAME_INPUT_R, "Input R", U_NONE, R_AUDIO, F_IN, 0, 0, 0, 0, 0, 0    },
    { PORT_NAME_OUTPUT_L, "Output L", U_NONE, R_AUDIO, F_OUT, 0, 0, 0, 0, 0, 0    },
    { PORT_NAME_OUTPUT_R, "Output R", U_NONE, R_AUDIO, F_OUT, 0, 0, 0, 0, 0, 0    },
    { "gain", "Output gain", U_GAIN_AMP, R_CONTROL, F_IN | F_LOG | F_UPPER | F_LOWER | F_STEP, 0, 10.0f, 1.0f, 0.1, 0, 0 },
NULL

Now we create complete description of plugin:

    struct test_plugin_metadata
    {
        static const plugin_metadata_t metadata;
    };

    const plugin_metadata_t test_plugin_metadata::metadata =
    {
        "TEST",
        "TEST",
        "TEST",
        &developers::v_sadovnikov,
        "test_plugin",
        "TEST",
        LSP_LADSPA_BASE + 1001,
        LSP_VERSION(1, 0, 0),
        test_classes,
        test_ports,
        stereo_plugin_port_groups
    };

OK, now we need to create derivatve from plugin_t.

namespace lsp
{
    class test_plugin: public plugin_t, public test_plugin_metadata
    {
        protected:
            IPort      *pIn[2]; // Input ports
            IPort      *pOut[2]; // Output ports
            IPort      *pGain; // Gain port
            float       fGain; // Gain value

        public:
            test_plugin();
            virtual ~test_plugin();

        public:
            virtual void init(IWrapper *wrapper);

            virtual void process(size_t samples);

            virtual void update_settings();
    };
}

And here it's implementation:

namespace lsp
{
    test_plugin::test_plugin(): plugin_t(metadata)
    {
        for (size_t i=0; i<2; ++i)
        {
            pIn[i]      = NULL;
            pOut[i]     = NULL;
        }
        pGain       = NULL;
        fGain       = 1.0f;
    }

    test_plugin::~test_plugin()
    {
    }

    void test_plugin::update_settings()
    {
        fGain           = pGain->getValue();
    }

    void test_plugin::init(IWrapper *wrapper)
    {
        plugin_t::init(wrapper);

        // Remember pointers to ports
        size_t port_id = 0;
        for (size_t i=0; i<2; ++i)
            pIn[i]      = vPorts[port_id++];
        for (size_t i=0; i<2; ++i)
            pOut[i]     = vPorts[port_id++];
        pGain           = vPorts[port_id++];
    }

    void test_plugin::process(size_t samples)
    {
        for (size_t i=0; i<2; ++i)
        {
            // Get data buffers
            float *in = pIn[i]->getBuffer<float>();
            if (in == NULL)
                continue;
            float *out = pOut[i]->getBuffer<float>();
            if (out == NULL)
                continue;

            // dsp::scale - optimized version for
            //  *(out++) = *(in++) * fGain    x   samples times
            dsp::scale(out, in, fGain, samples);
        }
    }
}

Nice! We got plugin. Now we should tell that it exists (currently without UI). We do this by describing module capabilities:

    // Test plugin
    MOD_PLUGIN(test_plugin)

Nice! If we have right written all includes, we can perform now:

> make clean && make && make install

sadko4u avatar Sep 28 '18 21:09 sadko4u

Related links:

plugin_t interface: https://github.com/sadko4u/lsp-plugins/blob/devel/include/core/plugin.h https://github.com/sadko4u/lsp-plugins/blob/devel/src/core/plugin.cpp

One of the simplest plugins example: https://github.com/sadko4u/lsp-plugins/blob/devel/include/plugins/comp_delay.h https://github.com/sadko4u/lsp-plugins/blob/devel/src/plugins/comp_delay.cpp

It's metadata: https://github.com/sadko4u/lsp-plugins/blob/devel/include/metadata/comp_delay.h https://github.com/sadko4u/lsp-plugins/blob/devel/src/metadata/comp_delay.cpp

Plugin definition as a module: https://github.com/sadko4u/lsp-plugins/blob/devel/include/metadata/modules.h

sadko4u avatar Sep 28 '18 21:09 sadko4u

@sadko4u why don't you turn these useful docs into a wiki page here on GitHub? It would be great for anyone willing to contribute to your plug-in suite.

monocasual avatar Mar 23 '19 14:03 monocasual

why don't you turn these useful docs into a wiki page here on GitHub? It would be great for anyone willing to contribute to your plug-in suite.

Yes, definitely it would be a good feature. I think I'll put the documentation to the wiki a little bit later.

sadko4u avatar Mar 23 '19 16:03 sadko4u