Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MIDI Utilities Feature Request #3

Open
techdude1996 opened this issue Sep 28, 2018 · 5 comments
Open

MIDI Utilities Feature Request #3

techdude1996 opened this issue Sep 28, 2018 · 5 comments
Labels
contribution Contributions from people to project discuss Discussing the issue good first issue Good for newcomers help wanted Extra attention is needed

Comments

@techdude1996
Copy link

@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?

@sadko4u
Copy link
Collaborator

sadko4u commented Sep 28, 2018

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
Copy link
Collaborator

sadko4u commented Sep 28, 2018

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 sadko4u added good first issue Good for newcomers contribution Contributions from people to project discuss Discussing the issue help wanted Extra attention is needed labels Sep 28, 2018
@monocasual
Copy link

@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.

@sadko4u
Copy link
Collaborator

sadko4u commented Mar 23, 2019

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
contribution Contributions from people to project discuss Discussing the issue good first issue Good for newcomers help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

2 participants