View Issue Details

IDProjectCategoryView StatusLast Update
0006639ardourbugspublic2020-04-19 20:17
ReporterSadKo Assigned Tox42  
PrioritynormalSeverityminorReproducibilityalways
Status closedResolutionno change required 
Product Version4.X git (version in description) 
Summary0006639: LADSPA and LV2 hosting problems ?
DescriptionHello!
I'm developing LADSPA-compatible plugin now and met some problems on official Ardour 4.2 build.

The first problem is with input control ports:
1. I define control port with HintDescriptor = LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_ABOVE
2. I specify LowerBound = -100.0f, UpperBound = 100.0f
3. The configuration interface of plugin does not allow to set value less than 0.0f for this port.

The second problem is with audio ports: I just add printf() to connect_port and get infinite stdout:
[ladspa.cpp:39] ladspa_connect_port: 0x2c02280, 0, 0x29d9240
[ladspa.cpp:39] ladspa_connect_port: 0x2c02280, 1, 0x29d9a80
[ladspa.cpp:39] ladspa_connect_port: 0x2c02280, 2, 0x29d9240
[ladspa.cpp:39] ladspa_connect_port: 0x2c02280, 3, 0x29d9a80
[ladspa.cpp:39] ladspa_connect_port: 0x2c02280, 0, 0x29d9240
[ladspa.cpp:39] ladspa_connect_port: 0x2c02280, 1, 0x29d9a80
[ladspa.cpp:39] ladspa_connect_port: 0x2c02280, 2, 0x29d9240
[ladspa.cpp:39] ladspa_connect_port: 0x2c02280, 3, 0x29d9a80
[ladspa.cpp:39] ladspa_connect_port: 0x2c02280, 0, 0x29d9240
[ladspa.cpp:39] ladspa_connect_port: 0x2c02280, 1, 0x29d9a80
[ladspa.cpp:39] ladspa_connect_port: 0x2c02280, 2, 0x29d9240
[ladspa.cpp:39] ladspa_connect_port: 0x2c02280, 3, 0x29d9a80

Also this happens with LV2:
[lv2.cpp:31] lv2_connect_port: 0x2b28910, 0, 0x289d0c0
[lv2.cpp:31] lv2_connect_port: 0x2b28910, 1, 0x289d900
[lv2.cpp:31] lv2_connect_port: 0x2b28910, 2, 0x289d0c0
[lv2.cpp:31] lv2_connect_port: 0x2b28910, 3, 0x289d900
[lv2.cpp:31] lv2_connect_port: 0x2b28910, 0, 0x28a12c0
[lv2.cpp:31] lv2_connect_port: 0x2b28910, 1, 0x28a1b00
[lv2.cpp:31] lv2_connect_port: 0x2b28910, 2, 0x28a12c0
[lv2.cpp:31] lv2_connect_port: 0x2b28910, 3, 0x28a1b00
[lv2.cpp:31] lv2_connect_port: 0x2b28910, 0, 0x28a12c0
[lv2.cpp:31] lv2_connect_port: 0x2b28910, 1, 0x28a1b00
[lv2.cpp:31] lv2_connect_port: 0x2b28910, 2, 0x28a12c0
[lv2.cpp:31] lv2_connect_port: 0x2b28910, 3, 0x28a1b00

Also when I specify Toggle control (both LADSPA and LV2) and declare minimium and maximum values, the result values of the toggle control are always 0.0f and 1.0f even if minimum value is -1.0f.
Don't know if it is a real problem but has to be noticed.
TagsNo tags attached.

Activities

x42

2015-10-14 22:07

administrator   ~0017459

Is the code for that plugin available?

Other LADSPA plugins work just fine with a range spanning zero. e.g swh's gate (ID 1921) has a threshold -70, +20

Infinite loop when connecting definitely suggests some other issue.

http://lv2plug.in/ns/lv2core/#toggled "Indicates that the data item should be considered a Boolean toggle. Data less than or equal to zero should be considered off or false, and data above zero should be considered on or true." The range is irrelevant.
(also note http://lv2plug.in/ns/lv2core/#minimum and #maximum. those are soft-limits, the plugin must be able to cope with any value).

2015-10-15 15:08

 

test.cpp (15,554 bytes)   
#include <sys/types.h>
#include <stddef.h>
#include <string.h>
#include <stdio.h>
#include <math.h>

#include <ladspa.h>

namespace xxx
{

    class StaticInitializer
    {
        public:
            typedef void (*func_t)();

        private:
            func_t init;
            func_t fini;

        public:
            StaticInitializer(func_t f_init, func_t f_fini)
            {
                init = f_init;
                fini = f_fini;
                if (init != NULL)
                    init();
            }

            ~StaticInitializer()
            {
                if (fini != NULL)
                    fini();
            }
    };

    enum flags_t
    {
        F_NONE          = 0,        // Input
        F_UPPER         = (1 << 0), // Upper-limit defined
        F_LOWER         = (1 << 1), // Lower-llmit defined
        F_STEP          = (1 << 2), // Step defined
        F_LOG           = (1 << 3)  // Logarithmic scale
    };

    enum unit_t
    {
        U_TOGGLE,
        U_VALUE,
        U_SECONDS,
        U_MILLIS,
        U_GAIN,
        U_PERCENT,
        U_METERS,
        U_SAMPLES,
        U_HZ,
        U_INDEX
    };

    enum plugin_class_t
    {
        C_DELAY,
            C_REVERB,
        C_DISTORTION,
            C_WAVESHAPER,
        C_DYNAMICS,
            C_AMPLIFIER,
            C_COMPRESSOR,
            C_ENVELOPE,
            C_EXPANDER,
            C_GATE,
            C_LIMITER,
        C_FILTER,
            C_ALLPASS,
            C_BANDPASS,
            C_COMB,
            C_EQ,
                C_MULTI_EQ,
                C_PARA_EQ,
            C_HIGHPASS,
            C_LOWPASS,
        C_GENERATOR,
            C_CONSTANT,
            C_INSTRUMENT,
            C_OSCILLATOR,
        C_MODULATOR,
            C_CHORUS,
            C_FLANGER,
            C_PHASER,
        C_SIMULATOR,
        C_SPATIAL,
        C_SPECTRAL,
            C_PITCH,
        C_UTILITY,
            C_ANALYSER,
            C_CONVERTER,
            C_FUNCTION,
            C_MIXER
    };

    typedef struct control_t
    {
        const char     *id;         // Control ID
        const char     *name;       // Control name
        unit_t          unit;       // Units
        int             flags;      // Flags
        float           min;        // Minimum value
        float           max;        // Maximum value
        float           start;      // Initial value
        float           step;       // Change step
        const char    **items;      // Items for list
    } parameter_t;

    typedef struct port_t
    {
        const char     *id;         // Port ID
        const char     *name;       // Port name
    } port_t;

    typedef struct plugin_metadata_t
    {
        const char             *name;           // Plugin description
        const char             *author;         // Author
        const int              *classes;        // List of plugin classes terminated by negative value
        const port_t           *in_buffers;     // List of data input ports
        const port_t           *out_buffers;    // List of data output ports
        const control_t        *in_controls;    // List of input controls
        const control_t        *out_controls;   // List of output controls
    } plugin_metadata_t;

    class plugin
    {
        public:
            plugin() {};

            virtual ~plugin() {};

        public:
            virtual void init(int sample_rate) {};
            virtual void destroy() {};

            virtual void activate() {};
            virtual void deactivate() {};
            virtual void bind(size_t port, void *data) {};
            virtual void update_settings() {};

            virtual void run(size_t samples) {};
            virtual void process(size_t samples) {};
    };

    class test_plugin: public plugin
    {
        public:
            static const plugin_metadata_t metadata;

            static const float DETECT_TIME_MIN          =   1.0f;
            static const float DETECT_TIME_MAX          =   100.0f;
            static const float DETECT_TIME_DFL          =   10.0f;
            static const float DETECT_TIME_STEP         =   1.0f;

        public:
            test_plugin() {};

            virtual ~test_plugin() {};
    };

    static const port_t input_ports[] =
    {
        { "in_a", "Input A" },
        { "in_b", "Input B" },
        { NULL }
    };

    static const port_t output_ports[] =
    {
        { "out_a", "Output A" },
        { "out_b", "Output B" },
        { NULL }
    };

    static const control_t input_controls[] =
    {
        { "sel_time",       "Selected time",    U_MILLIS,   F_UPPER | F_LOWER | F_STEP, - test_plugin::DETECT_TIME_MAX, test_plugin::DETECT_TIME_MAX, 0, 1, NULL },
        { NULL, NULL }
    };

    static const control_t output_controls[] =
    {
        { "best_time",      "Best time",        U_MILLIS,   F_NONE, 0, 0, 0, 0, NULL },
        { NULL, NULL }
    };

    static const int classes[] = { C_UTILITY, C_ANALYSER, -1 };

    const plugin_metadata_t test_plugin::metadata =
    {
        // Name
        "test_plugin",
        // Author
        "Vladimir Sadovnikov",
        // Classes
        classes,
        // Inputs
        input_ports,
        // Outputs
        output_ports,
        // Input controls
        input_controls,
        // Output controls
        output_controls
    };

    LADSPA_Handle ladspa_instantiate(
        const struct _LADSPA_Descriptor * Descriptor,
        unsigned long                     SampleRate)
    {
        plugin *p = NULL;
        size_t id = 0x10000;

        #define PLUGIN_DEF(plugin) \
            if ((!p) && (Descriptor->UniqueID == id)) \
                p = new plugin(); \
            id++;

        PLUGIN_DEF(test_plugin);
        #undef PLUGIN_DEF

        if (p)
            p->init(SampleRate);

        return reinterpret_cast<LADSPA_Handle>(p);
    }

    void ladspa_connect_port(
        LADSPA_Handle Instance,
        unsigned long Port,
        LADSPA_Data * DataLocation)
    {
        printf("ladspa_connect_port %p, %d, %p\n", Instance, (int)Port, DataLocation);
        plugin *p = reinterpret_cast<plugin *>(Instance);
        p->bind(Port, DataLocation);
    }

    void ladspa_activate(LADSPA_Handle Instance)
    {
        plugin *p = reinterpret_cast<plugin *>(Instance);
        p->activate();
    }

    void ladspa_run(LADSPA_Handle Instance, unsigned long SampleCount)
    {
        plugin *p = reinterpret_cast<plugin *>(Instance);
        p->run(SampleCount);
    }

    void ladspa_deactivate(LADSPA_Handle Instance)
    {
        plugin *p = reinterpret_cast<plugin *>(Instance);
        p->deactivate();
    }

    void ladspa_cleanup(LADSPA_Handle Instance)
    {
        plugin *p = reinterpret_cast<plugin *>(Instance);
        p->destroy();
        delete p;
    }

    LADSPA_Descriptor *ladspa_descriptors = NULL;
    size_t ladspa_descriptors_count    = 0;

    const char *decode_unit(size_t unit)
    {
        switch (unit)
        {
            case U_SECONDS: return "s";
            case U_MILLIS:  return "ms";
            case U_PERCENT: return "%";
            case U_METERS:  return "m";
            case U_SAMPLES: return "samples";
            case U_HZ:      return "Hz";
        }

        return NULL;
    }

    const char *ladspa_add_units(const char *s, size_t units)
    {
        char buf[256];
        const char *unit = decode_unit(units);
        if (unit == NULL)
            return strdup(s);

        snprintf(buf, sizeof(buf) - 1, "%s (%s)", s, unit);
        return strdup(buf);
    }

    void ladspa_make_descriptor(LADSPA_Descriptor *d, unsigned long id, const char *label, const plugin_metadata_t &m)
    {
        d->UniqueID     = id;
        d->Label        = label;
        d->Properties   = LADSPA_PROPERTY_HARD_RT_CAPABLE;
        d->Name         = m.name;
        d->Maker        = m.author;
        d->Copyright    = "(C)";
        d->PortCount    = 0;

        // Calculate number of ports
        for (const port_t *p = m.in_buffers; p->name != NULL; ++p)
            d->PortCount ++;
        for (const port_t *p = m.out_buffers; p->name != NULL; ++p)
            d->PortCount ++;
        for (const control_t *p = m.in_controls; (p->id != NULL) && (p->name != NULL); ++p)
            d->PortCount ++;
        for (const control_t *p = m.out_controls; (p->id != NULL) && (p->name != NULL); ++p)
            d->PortCount ++;

        LADSPA_PortDescriptor *p_descr      = new LADSPA_PortDescriptor[d->PortCount];
        const char **p_name                 = new const char *[d->PortCount];
        LADSPA_PortRangeHint *p_hint        = new LADSPA_PortRangeHint[d->PortCount];

        d->PortDescriptors                  = p_descr;
        d->PortNames                        = p_name;
        d->PortRangeHints                   = p_hint;
        d->ImplementationData               = NULL;

        for (const port_t *p = m.in_buffers; p->name != NULL; ++p)
        {
            *p_descr                = LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO;
            *p_name                 = p->name;
            p_hint->HintDescriptor  = 0;
            p_hint->LowerBound      = 0.0f;
            p_hint->UpperBound      = 0.0f;

            p_descr++;
            p_name++;
            p_hint++;
        }

        for (const port_t *p = m.out_buffers; p->name != NULL; ++p)
        {
            *p_descr                = LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO;
            *p_name                 = p->name;
            p_hint->HintDescriptor  = 0;
            p_hint->LowerBound      = 0.0f;
            p_hint->UpperBound      = 0.0f;

            p_descr++;
            p_name++;
            p_hint++;
        }

        for (size_t i=0; i<2; ++i)
        {
            const control_t *p = (i == 0) ? m.in_controls : m.out_controls;

            while ((p->id != NULL) && (p->name != NULL))
            {
                *p_descr                = (i == 0) ? LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL : LADSPA_PORT_OUTPUT | LADSPA_PORT_CONTROL;
                *p_name                 = ladspa_add_units(p->name, p->unit);
                p_hint->HintDescriptor  = 0;

                if (p->unit == U_TOGGLE)
                {
                    p_hint->HintDescriptor |= LADSPA_HINT_TOGGLED | LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_BELOW;
                    p_hint->HintDescriptor |= (p->start > 0) ? LADSPA_HINT_DEFAULT_1 : LADSPA_HINT_DEFAULT_0;
                    p_hint->LowerBound      = -1.0f;
                    p_hint->UpperBound      = 1.0f;
                }
                else
                {
                    printf("port name=%s\n", p->name);
                    if (p->flags & F_LOWER)
                    {
                        p_hint->HintDescriptor |= LADSPA_HINT_BOUNDED_BELOW;
                        if (p->min == p->start)
                            p_hint->HintDescriptor |= LADSPA_HINT_DEFAULT_MINIMUM;
                        p_hint->LowerBound      = p->min;
                        printf("lower=%.3f\n", p_hint->LowerBound);
                    }
                    if (p->flags & F_UPPER)
                    {
                        p_hint->HintDescriptor |= LADSPA_HINT_BOUNDED_ABOVE;
                        if (p->max == p->start)
                            p_hint->HintDescriptor |= LADSPA_HINT_DEFAULT_MAXIMUM;
                        p_hint->UpperBound      = p->max;
                        printf("upper=%.3f\n", p_hint->UpperBound);
                    }
                    if (p->flags & F_LOG)
                        p_hint->HintDescriptor |= LADSPA_HINT_LOGARITHMIC;
                    if (p->unit == U_INDEX)
                        p_hint->HintDescriptor  |= LADSPA_HINT_INTEGER;

                    if ((p_hint->HintDescriptor & LADSPA_HINT_DEFAULT_MASK) == LADSPA_HINT_DEFAULT_NONE)
                    {
                        if (p->start == 1.0f)
                            p_hint->HintDescriptor |= LADSPA_HINT_DEFAULT_1;
                        else if (p->start == 0.0f)
                            p_hint->HintDescriptor |= LADSPA_HINT_DEFAULT_0;
                        else if (p->start == 100.0f)
                            p_hint->HintDescriptor |= LADSPA_HINT_DEFAULT_100;
                        else if (p->start == 440.0f)
                            p_hint->HintDescriptor |= LADSPA_HINT_DEFAULT_440;
                        else if ((p->flags & (F_LOWER | F_UPPER))  == (F_LOWER | F_UPPER))
                        {
                            float factor = (p->flags & F_LOG) ?
                                (logf(p->start) - logf(p->min)) / (logf(p->max) - logf(p->min)) :
                                (p->start - p->min) / (p->max - p->min);

                            if (factor <= 0.33)
                                p_hint->HintDescriptor |= LADSPA_HINT_DEFAULT_LOW;
                            else if (factor >= 0.66)
                                p_hint->HintDescriptor |= LADSPA_HINT_DEFAULT_HIGH;
                            else
                                p_hint->HintDescriptor |= LADSPA_HINT_DEFAULT_MIDDLE;
                        }
                        else if (p->flags & F_LOWER)
                            p_hint->HintDescriptor |= LADSPA_HINT_DEFAULT_MINIMUM;
                        else if (p->flags & F_UPPER)
                            p_hint->HintDescriptor |= LADSPA_HINT_DEFAULT_MAXIMUM;
                    }
                }

                p_descr++;
                p_name++;
                p_hint++;
                p++;
            }
        }

        d->instantiate          = ladspa_instantiate;
        d->connect_port         = ladspa_connect_port;
        d->activate             = ladspa_activate;
        d->run                  = ladspa_run;
        d->run_adding           = NULL;
        d->set_run_adding_gain  = NULL;
        d->deactivate           = ladspa_deactivate;
        d->cleanup              = ladspa_cleanup;
    }

    void ladspa_gen_descriptors()
    {
        printf("ladspa_gen_descriptors\n");
        if (ladspa_descriptors != NULL)
            return;

        // Calculate number of plugins
        ladspa_descriptors_count    = 0;
        #define PLUGIN_DEF(plugin) ladspa_descriptors_count++;
        PLUGIN_DEF(test_plugin);
        #undef PLUGIN_DEF

        // Now allocate descriptors
        ladspa_descriptors          = new LADSPA_Descriptor[ladspa_descriptors_count];
        LADSPA_Descriptor *d        = ladspa_descriptors;
        size_t id                   = 0;

        #define PLUGIN_DEF(plugin) ladspa_make_descriptor(&d[id], 0x10000 + id, "http://plugin.org/plugins/ladspa/test_plugin", plugin::metadata); id++;
        PLUGIN_DEF(test_plugin);
        #undef PLUGIN_DEF
    };

    void ladspa_drop_descriptors()
    {
        printf("ladspa_drop_descriptors\n");
        if (ladspa_descriptors == NULL)
            return;

        LADSPA_Descriptor *d = ladspa_descriptors;
        while (ladspa_descriptors_count--)
        {
            delete [] d->PortNames;
            delete [] d->PortDescriptors;
            delete [] d->PortRangeHints;
        }

        delete [] ladspa_descriptors;
        ladspa_descriptors = NULL;
    };

    StaticInitializer ladspa_init(ladspa_gen_descriptors, ladspa_drop_descriptors);
}

const LADSPA_Descriptor * ladspa_descriptor(unsigned long index)
{
    using namespace xxx;
    return (index < ladspa_descriptors_count) ? &ladspa_descriptors[index] : NULL;
}

test.cpp (15,554 bytes)   

SadKo

2015-10-15 15:13

reporter   ~0017461

Hello! I can't reproduce it with reduced code (see test.cpp). Probably it's a compiler issue:
{ "sel_time", "Selected time", U_MILLIS, F_UPPER | F_LOWER | F_STEP, - test_plugin::DETECT_TIME_MAX, test_plugin::DETECT_TIME_MAX, 0, 1, NULL },

The problem is that "- test_plugin::DETECT_TIME_MAX" was interpreted in original implementation as 0 (compiler bug?).

But the connect_port() still runs infinitely after plugin is placed into mixer strip. You can compile test plugin and watch ardour's stdout.

SadKo

2015-10-15 15:27

reporter   ~0017462

Okay, now I understood what was going on. Constant '- test_plugin::DETECT_TIME_MAX' was as routine and placed into initialization list. And descriptors were built with initialization-list primitive, too. So it could be simply uninitialized when the descriptors were instantiated.

x42

2015-10-15 17:39

administrator   ~0017463

The "infinite" stdout is not infinite. Ports are [re]connected every process cycle. (the pointers may change any time).

printf("ladspa_run %d\n", SampleCount); to ladspa_run() and you should see e.g.

ladspa_connect_port 0 ...
ladspa_connect_port 1 ...
ladspa_run 1024
ladspa_connect_port 0 ...
ladspa_connect_port 1 ...
ladspa_run 1024

x42

2015-10-15 18:05

administrator   ~0017464

same for LV2. connect_port() is called every cycle.

Usually the control-ports stay (same pointer) and audio buffers change every cycle, but you can't rely on this. The port pointers are only valid during run().

http://lv2plug.in/doc/html/group__lv2core.html#a4d904937a1bd27cb5f5478f95c708b16

SadKo

2015-10-15 19:27

reporter   ~0017466

Okay them, it's not a bug. Thank you for information.

SadKo

2015-10-15 19:28

reporter   ~0017467

Not a problem

x42

2015-10-15 23:59

administrator   ~0017468

marking this as resolved. but feel free to ask more questions..

Good resource in general is the linux-audio-dev email list http://lists.linuxaudio.org/listinfo/linux-audio-dev and the #lad IRC chat channel on freenode http://webchat.freenode.net/?channels=lad

system

2020-04-19 20:17

developer   ~0023550

Issue has been closed automatically, by Trigger Close Plugin.
Feel free to re-open with additional information if you think the issue is not resolved.

Issue History

Date Modified Username Field Change
2015-10-14 12:15 SadKo New Issue
2015-10-14 22:07 x42 Note Added: 0017459
2015-10-15 15:08 SadKo File Added: test.cpp
2015-10-15 15:13 SadKo Note Added: 0017461
2015-10-15 15:27 SadKo Note Added: 0017462
2015-10-15 17:39 x42 Note Added: 0017463
2015-10-15 18:05 x42 Note Added: 0017464
2015-10-15 19:27 SadKo Note Added: 0017466
2015-10-15 19:28 SadKo Note Added: 0017467
2015-10-15 23:59 x42 Note Added: 0017468
2015-10-15 23:59 x42 Status new => resolved
2015-10-15 23:59 x42 Resolution open => no change required
2015-10-15 23:59 x42 Assigned To => x42
2020-04-19 20:17 system Note Added: 0023550
2020-04-19 20:17 system Status resolved => closed