View Issue Details

IDProjectCategoryView StatusLast Update
0006679ardourbugspublic2015-12-13 07:56
ReporterSadKo Assigned To 
PrioritynormalSeverityminorReproducibilityalways
Status newResolutionopen 
PlatformLinux 
Product Version4.2 
Summary0006679: LV2UI: output port values notification when instantiating UI
DescriptionWhile developing UI to the plugin met some strange issue.

Here is part of reduced RDF:

@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix ui: <http://lv2plug.in/ns/extensions/ui#> .

<plugin-ui-url>
        a ui:GtkUI ;
        ui:binary <file.so> ;
        ui:requiredFeature ui:makeResident ;
                
        ui:portNotification [
                ui:plugin <plugin-url> ;
                ui:portIndex 0 ;
                ui:protocol ui:floatProtocol ;
        ] , [
                ui:plugin <plugin-url> ;
                ui:portIndex 1 ;
                ui:protocol ui:peakProtocol ;
        ] .

<plugin-url>
        a lv2:Plugin ;
        lv2:port [
                a lv2:OutputPort, lv2:ControlPort ;
                lv2:index 0 ;
                lv2:symbol "port_a" ;
                lv2:name "Port A" ;
        ] , [
                a lv2:OutputPort, lv2:ControlPort ;
                lv2:index 1 ;
                lv2:symbol "port_b" ;
                lv2:name "Port B" ;
        ] .

What I expected:
When instantiating UI, lv2ui_port_event is called for ALL lv2:ControlPort ports listed with ui:portNotification so my UI can correcly display control values and metering values even if they're not changing due to inactive playback.

What I got:
1. While instantiating UI, Ardour sends configuration only for lv2:InputPort ports. The value of lv2:OutputPort is not sent to the UI, so there is no way (that I currently know) to display the correct values of output control ports in the UI until the plugin changes them.

2. When I changed protocol to ui:peakProtocol, I expected that the values of output ports will be sent to the UI periodically in LV2UI_Peak_Data structure. But actually got that Ardour does not support peakProtocol and still sends data using floatProtocol.

Is there any way to fix such behaviour?
TagsNo tags attached.

Activities

x42

2015-11-18 12:38

administrator   ~0017619

GUI notification behaviour has been significantly changed in 4.4-git. This will address the issue you're having.

Ardour still always only sends changes, but since 4.4.154, Ardour also sends the initial values to the GUI every time the GUI is [re]opened.

Until now, most plugin GUIs explicitly ask their DSP backend to re-transfer GUI state every time the GUI is displayed. That mechanism is still useful in cases where the GUI should not miss a value (e.g acknowledge meter peak-hold). This can be accomplished using a handshake (force change on input -> triggers change on output) or using an Atom Control message.


PS. The output ports have a mandatory min/max range and ui:makeResident is not a valid "requiredFeature".
I suggest to check http://lv2plug.in/book/

SadKo

2015-11-18 13:06

reporter   ~0017620

> The output ports have a mandatory min/max range
Thank you for a notice for this. Will add to RDF. But what to deal with sample rate-dependent values? I can calculate values for SR like 384000 or above but theoretically it can be much greater, so it's not the complete final solution.

> This can be accomplished using a handshake (force change on input -> triggers change on output) or using an Atom Control message.
The first method isn't good for me because the output values depend directly on input signal (Audio ports). The second way - need to look documentation. But changing output values even on 1e-20 IMHO is really bad idea.

> I suggest to check http://lv2plug.in/book/
Thanks for a link, I'm currently inspecting Calf and Invada packages for LV2 programming issues.

x42

2015-11-18 15:40

administrator   ~0017621

A port with the property http://lv2plug.in/ns/lv2core/#sampleRate
"Indicates that any bounds specified should be interpreted as multiples of the sample rate."

-----8<----
 lv2:minimum 0.0 ;
 lv2:maximum 1.0 ;
 lv2:portProperty lv2:sampleRate ;
----->8----



Regarding update notification:
A quick dirty hack to force the update is to indeed add some dithering noise.


The GUI runs at much slower rate (usually 25Hz) than the DSP and since the UI is not in realtime, many output values are simply not transmitted to the UI in the first place. In most cases the GUI receives a port_event() every 25Hz or so if and only if the value on the port was changed.

Since both http://lv2plug.in/ns/extensions/ui/#peakProtocol and http://lv2plug.in/ns/extensions/ui/#floatProtocol are not strong "... host SHOULD call...", a Plugin cannot rely on those anyway and I don't know a host that implements them.

The GUI may also not be open all the time, so the DSP backend needs to keep track anyway.


As for using Atom Sequence notification: search for "send_state_to_ui" and "resend_peak" in https://github.com/x42/fil4.lv2/blob/master/src/lv2.c

Another hacked solution can be found in meters.lv2: a dedicated control input with "lv2:portProperty pprop:notOnGUI" is used to tell the DSP to temporarily force a change (that's not needed anymore with recent Ardour 4.4-git)


Anyway, just update to Ardour4.4-git or get a test version from http://nightly.ardour.org/

SadKo

2015-11-20 10:42

reporter   ~0017633

Okay, need to implement again many hacks for the simplest things to become back-compatible with the host.

Thank you for information, x42.

LV2 with so many hacks (called 'extensions') becomes really complicated standard starting from 'official documentation'.

I think it's not a good idea when on the official LV2 site is written:
"What not to do: If you are new to LV2, do not look at the specifications and API references first! These references provide detail, but not necessarily a clear high-level view. Start with the suggestions below, and check the references later when you need specifics."

It's just a lack of normal documentation.

SadKo

2015-11-20 18:07

reporter   ~0017636

Hi again!

Today i've refactored my code to support Atoms.

Is it possible to send notification with LV2_Atom_Float primitive?

Shortly the code looks like:

LV2_Atom_Forge *forge = pForge->forge;
lv2_atom_forge_set_buffer(forge, obj_buf, sizeof(obj_buf));

LV2_Atom_Float *atom = reinterpret_cast<LV2_Atom_Float *>(lv2_atom_forge_float(forge, value));

trace("buffer = %p", atom);
trace("atom = %p {%d, %d, %f}", atom, atom->atom.type, atom->atom.size, atom->body);
trace("write(%p, %d, %d, %d, %p)", pController, int(nID), int(lv2_atom_total_size(&atom->atom)), int(pForge->uridEventTransfer), atom);

pWrite(pController, nID, lv2_atom_total_size(&atom->atom), pForge->uridEventTransfer, atom);
trace("written");

This code runs on the last stage of LV2UI_Instantiate function.

I get such messages in stdout:
setValue: buffer = 0x7ffda45787c0
setValue: atom = 0x7ffda45787c0 {8, 4, 1.000000}
setValue: write(0x3763040, 4, 12, 4, 0x7ffda45787c0)
setValue: written

And then Ardour crashes:
lv2ui_port_event: notify: port_id=5
lv2ui_port_event: notified
lv2ui_port_event: notify: port_id=6
lv2ui_port_event: notified
lv2ui_port_event: notify: port_id=7
lv2ui_port_event: notified
lv2ui_port_event: notify: port_id=8
lv2ui_port_event: notified
lv2ui_port_event: notify: port_id=9
lv2ui_port_event: notified
Segmentation fault

What actually I'm doing wrong?

x42

2015-11-20 18:39

administrator   ~0017637

needs more context. what is pWrite? what is nID? does nID correspond to a LV2 Atom port?

for basic testing, use `jalv.gtk` as LV2 host. that also has a --dump option to print UI<>plugin communication. the mailing list (linked from http://lv2plug.in/) may also be more appropriate to help out.

SadKo

2015-11-20 19:24

reporter   ~0017638

Here it is:
pForge is a wrapper for lv2_forge.
nID is LV2 port number
pWrite is port write function
uridEventTransfer is urid of event transfer taken from URID map object.

x42

2015-11-20 19:38

administrator   ~0017639

I was thinking of source-code and gdb backtrace.

There is usually a single Atom port for each direction.

from the debug messages it seems you're trying to send Atom-Events to normal Control ports, that won't work. But without seeing the .tll and source I can't tell.

SadKo

2015-11-20 20:00

reporter   ~0017640

Here is the part of RDF:

@prefix lv2: <http://lv2plug.in/ns/lv2core#> .
@prefix atom: <http://lv2plug.in/ns/ext/atom#> .
@prefix urid: <http://lv2plug.in/ns/ext/urid#> .

<plugin-uri>
        a lv2:Plugin, lv2:UtilityPlugin, lv2:AnalyserPlugin ;
        <blablabla> ;
        lv2:requiredFeature urid:map ;
        lv2:port [
                a lv2:InputPort, lv2:AudioPort ;
                lv2:index 0 ;
                lv2:symbol "in_a" ;
                lv2:name "Input A" ;
        ] , [
                a lv2:InputPort, lv2:AudioPort ;
                lv2:index 1 ;
                lv2:symbol "in_b" ;
                lv2:name "Input B" ;
        ] , [
                a lv2:OutputPort, lv2:AudioPort ;
                lv2:index 2 ;
                lv2:symbol "out_a" ;
                lv2:name "Output A" ;
        ] , [
                a lv2:OutputPort, lv2:AudioPort ;
                lv2:index 3 ;
                lv2:symbol "out_b" ;
                lv2:name "Output B" ;
        ] , [
                a lv2:InputPort, atom:AtomPort ;
                atom:bufferType atom:Float ;
                lv2:index 4 ;
                lv2:symbol "ui_sync_in" ;
                lv2:name "UI Sync IN" ;
                lv2:default 0.000000 ;
        ] , [
                a lv2:InputPort, lv2:ControlPort ;
                lv2:index 5 ;
                lv2:symbol "bypass" ;
                lv2:name "Bypass" ;
                lv2:portProperty lv2:toggled ;
                lv2:minimum -1.000000 ;
                lv2:maximum 1.000000 ;
                lv2:default 0.000000 ;
        ]
        .

<plugin-ui>
lsp_gtk2:phase_detector
        a ui:GtkUI ;
        ui:binary <lsp-plugins-gtk2.so> ;

        ui:portNotification [
                ui:plugin lsp:phase_detector ;
                ui:portIndex 4 ;
                ui:protocol atom:eventTransfer ;
        ] , [
                ui:plugin lsp:phase_detector ;
                ui:portIndex 5 ;
                ui:protocol ui:floatProtocol ;
        ]
        .

The last GDB output for non-dbg version:
[lv2ui_gtk.cpp:139] lv2ui_instantiate: Return handle
[lv2ui_gtk.cpp:156] lv2ui_port_event: notify: port_id=5
[lv2ui_gtk.cpp:164] lv2ui_port_event: notified
[lv2ui_gtk.cpp:156] lv2ui_port_event: notify: port_id=6
[lv2ui_gtk.cpp:164] lv2ui_port_event: notified
[lv2ui_gtk.cpp:156] lv2ui_port_event: notify: port_id=7
[lv2ui_gtk.cpp:164] lv2ui_port_event: notified
[lv2ui_gtk.cpp:156] lv2ui_port_event: notify: port_id=8
[lv2ui_gtk.cpp:164] lv2ui_port_event: notified
[lv2ui_gtk.cpp:156] lv2ui_port_event: notify: port_id=9
[lv2ui_gtk.cpp:164] lv2ui_port_event: notified

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7fffd07e5700 (LWP 9318)]
0x00007ffff709e380 in lv2_evbuf_get_size () from /opt/Ardour-4.2.0/lib/libardour.so.3
Missing separate debuginfos, use: zypper install jamin-debuginfo-0.95.0-257.1.4.x86_64 ladspa-AMB-debuginfo-0.6.1-3.1.2.x86_64 ladspa-FIL-debuginfo-0.3.0-3.1.2.x86_64 ladspa-LEET-plugin-debuginfo-0.2-2.1.x86_64 ladspa-MCP-debuginfo-0.4.0-3.1.2.x86_64 ladspa-REV-debuginfo-0.3.1-3.1.2.x86_64 ladspa-VCO-debuginfo-0.3.0-3.1.2.x86_64 ladspa-WAH-debuginfo-0.0.2-3.1.x86_64 ladspa-alienwah-debuginfo-1.13-2.1.2.x86_64 ladspa-aweight-debuginfo-0.3.0-3.1.x86_64 ladspa-blepvco-debuginfo-0.1.0-3.1.2.x86_64 ladspa-blop-debuginfo-0.2.8-3.1.2.x86_64 ladspa-bs2b-debuginfo-0.9.1-4.1.x86_64 ladspa-caps-debuginfo-0.4.4-2.1.5.x86_64 ladspa-cmt-debuginfo-1.15-3.1.2.x86_64 ladspa-debuginfo-1.13-26.1.2.x86_64 ladspa-foo-plugins-debuginfo-1.2-4.1.x86_64 ladspa-guitarix-debuginfo-0.28.3-2.1.11.x86_64 ladspa-lemux-debuginfo-0.2-4.1.x86_64 ladspa-lgv-plugins-debuginfo-0.1-3.1.x86_64 ladspa-matched-debuginfo-1-3.1.2.x86_64 ladspa-njl-debuginfo-0.2.1-2.1.x86_64 ladspa-omins-debuginfo-0.2.1-4.1.x86_64 ladspa-preamp-debuginfo-2-2.1.2.x86_64 ladspa-pvoc-debuginfo-0.1.12-2.1.2.x86_64 ladspa-sooperlooper-debuginfo-0.93-2.1.2.x86_64 ladspa-super-60-debuginfo-1-2.1.2.x86_64 ladspa-swh-plugins-debuginfo-0.4.15-2.1.2.x86_64 ladspa-tap-plugins-debuginfo-0.7.1-2.1.2.x86_64 ladspa-vcf-debuginfo-0.0.5-3.1.2.x86_64 ladspa-vlevel-debuginfo-0.5-3.1.2.x86_64 ladspa-vocoder-debuginfo-0.3-3.1.2.x86_64 ladspa-wasp-debuginfo-0.1.4-3.1.x86_64 libX11-6-debuginfo-1.6.2-5.1.2.x86_64 libX11-xcb1-debuginfo-1.6.2-5.1.2.x86_64 libXau6-debuginfo-1.0.8-5.1.2.x86_64 libXcursor1-debuginfo-1.1.14-5.1.2.x86_64 libXext6-debuginfo-1.3.3-2.1.2.x86_64 libXfixes3-debuginfo-5.0.1-4.1.2.x86_64 libXrender1-debuginfo-0.9.8-4.1.2.x86_64 libasound2-debuginfo-1.0.28-6.1.4.x86_64 libboost_system1_54_0-debuginfo-1.54.0-10.1.3.x86_64 libbs2b0-debuginfo-3.1.0-8.1.2.x86_64 libgcc_s1-debuginfo-4.8.3+r212056-2.2.4.x86_64 libjack0-debuginfo-1.9.10-1.1.x86_64 libstdc++6-debuginfo-4.8.3+r212056-2.2.4.x86_64 libxcb-render0-debuginfo-1.11-2.1.2.x86_64 libxcb-shm0-debuginfo-1.11-2.1.2.x86_64 libxcb1-debuginfo-1.11-2.1.2.x86_64 rubberband-ladspa-debuginfo-1.6.0-12.1.2.x86_64

Here is the stack trace of the problem thread:
Thread 26 (Thread 0x7fffd07e5700 (LWP 9318)):
#0 0x00007ffff709e380 in lv2_evbuf_get_size () from /opt/Ardour-4.2.0/lib/libardour.so.3
0000001 0x00007ffff709e40d in lv2_evbuf_end () from /opt/Ardour-4.2.0/lib/libardour.so.3
#2 0x00007ffff7097f22 in ARDOUR::LV2Plugin::connect_and_run(ARDOUR::BufferSet&, ARDOUR::ChanMapping, ARDOUR::ChanMapping, unsigned int, long) () from /opt/Ardour-4.2.0/lib/libardour.so.3
#3 0x00007ffff6f152e1 in ARDOUR::PluginInsert::connect_and_run(ARDOUR::BufferSet&, unsigned int, long, bool, long) () from /opt/Ardour-4.2.0/lib/libardour.so.3
0000004 0x00007ffff6f15b69 in ARDOUR::PluginInsert::run(ARDOUR::BufferSet&, long, long, unsigned int, bool) () from /opt/Ardour-4.2.0/lib/libardour.so.3
0000005 0x00007ffff6f79e1e in ARDOUR::Route::process_output_buffers(ARDOUR::BufferSet&, long, long, unsigned int, int, bool) () from /opt/Ardour-4.2.0/lib/libardour.so.3
#6 0x00007ffff6f77a7e in ARDOUR::Route::passthru(ARDOUR::BufferSet&, long, long, unsigned int, int) () from /opt/Ardour-4.2.0/lib/libardour.so.3
#7 0x00007ffff6f82876 in ARDOUR::Route::no_roll(unsigned int, long, long, bool) () from /opt/Ardour-4.2.0/lib/libardour.so.3
0000008 0x00007ffff6e328b0 in ARDOUR::Graph::process_one_route(ARDOUR::Route*) () from /opt/Ardour-4.2.0/lib/libardour.so.3
0000009 0x00007ffff6e32bc2 in ARDOUR::Graph::run_one() () from /opt/Ardour-4.2.0/lib/libardour.so.3
0000010 0x00007ffff6e32f61 in ARDOUR::Graph::main_thread() () from /opt/Ardour-4.2.0/lib/libardour.so.3
0000011 0x000000000084abd3 in boost::function0<void>::operator()() const ()
0000012 0x00007fffe833a689 in ARDOUR::JACKAudioBackend::_start_process_thread(void*) () from /opt/Ardour-4.2.0/lib/backends/libjack_audiobackend.so
0000013 0x00007ffff0eec0a4 in start_thread () from /lib64/libpthread.so.0
0000014 0x00007fffee7d608d in clone () from /lib64/libc.so.6

SadKo

2015-11-20 20:32

reporter   ~0017641

Here this is the reduced code:

    class LV2AtomForge
    {
        private:
            LV2_Atom_Forge sForge;
            LV2_URID_Map *pMap;
            ssize_t nRefs;

        public:
            LV2_URID uridAtomTransfer;
            LV2_URID uridEventTransfer;

        public:
            LV2AtomForge(LV2_URID_Map *map)
            {
                nRefs = 1;
                pMap = map;
                lv2_atom_forge_init(&sForge, pMap);

                uridAtomTransfer = map->map(map->handle, LV2_ATOM__atomTransfer);
                uridEventTransfer = map->map(map->handle, LV2_ATOM__eventTransfer);
            }

            ~LV2AtomForge()
            {
                trace("destroy");
            }

            inline void bind() { nRefs ++; };
            inline void unbind() { if ((--nRefs) <= 0) delete this; }

            inline LV2_URID_Map *map() { return pMap; }
            inline LV2_Atom_Forge *forge() { return &sForge; }
    };

    class LV2UIPort: public IUIPort
    {
        protected:
            size_t nID;
            LV2UI_Write_Function pWrite;
            LV2UI_Controller pController;

        public:
            explicit LV2UIPort(size_t id, LV2UI_Controller ct, LV2UI_Write_Function wf)
            {
                nID = id;
                pWrite = wf;
                pController = ct;
            }
            virtual ~LV2UIPort() {}
    };


    class LV2UIAtomPort: public LV2UIPort
    {
        protected:
            size_t nID;
            LV2AtomForge *pForge;
            LV2_URID_Map *pMap;

        public:
            LV2UIAtomPort(size_t id, LV2UI_Controller ct, LV2UI_Write_Function wf, LV2AtomForge *forge):
                LV2UIPort(id, ct, wf)
            {
                nID = id;
                pForge = forge;
                pMap = forge->map();
                forge -> bind();
            }

            virtual ~LV2UIAtomPort()
            {
                pForge -> unbind();
                pForge = NULL;
                pMap = NULL;
            }
    };

    class LV2UIAtomFloatPort: public LV2UIAtomPort
    {
        protected:
            float fValue;

        public:
            explicit LV2UIAtomFloatPort(size_t id, LV2UI_Controller ct, LV2UI_Write_Function wf, LV2AtomForge *forge) :
                LV2UIAtomPort(id, ct, wf, forge)
            {
                fValue = 0.0f;
            }
            virtual ~LV2UIAtomFloatPort() { fValue = pMetadata->start; };

        public:
            virtual float getValue() { return fValue; }

            virtual void setValue(float value)
            {
                uint8_t obj_buf[1024];

                LV2_Atom_Forge *forge = pForge->forge();
                lv2_atom_forge_set_buffer(forge, obj_buf, sizeof(obj_buf));

                LV2_Atom_Float *atom = reinterpret_cast<LV2_Atom_Float *>(lv2_atom_forge_float(forge, value));

                trace("buffer = %p", atom);
                trace("atom = %p {%d, %d, %f}", atom, atom->atom.type, atom->atom.size, atom->body);
                trace("write(%p, %d, %d, %d, %p)",
                    pController, int(nID), int(lv2_atom_total_size(&atom->atom)), int(pForge->uridEventTransfer), atom);

                pWrite(pController, nID, lv2_atom_total_size(&atom->atom), pForge->uridEventTransfer, atom);
                trace("written");
            }

            virtual void notify(const void *buffer, size_t protocol, size_t size)
            {
                trace("notify = %p, %d, %d", buffer, int(protocol), int(size));
                if (protocol == pForge->uridEventTransfer)
                {
                    if (size == sizeof(LV2_Atom_Float))
                    {
                        fValue = (reinterpret_cast<const LV2_Atom_Float *>(buffer))->body;
                        notifyAll();
                    }
                }
            }
    };

LV2UI_Handle lv2ui_instantiate(
        const struct _LV2UI_Descriptor* descriptor,
        const char* plugin_uri,
        const char* bundle_path,
        LV2UI_Write_Function write_function,
        LV2UI_Controller controller,
        LV2UI_Widget* widget,
        const LV2_Feature* const* features)
    {

        // Scan for extensions
        LV2_URID_Map *map = NULL;

        for (size_t i=0; features[i]; ++i)
        {
            const LV2_Feature *f = features[i];

            if (!strcmp(f->URI, LV2_URID__map))
                map = reinterpret_cast<LV2_URID_Map *>(f->data);
        }

        LV2AtomForge *forge = (map != NULL) ? new LV2AtomForge(map) : NULL;

        // here plugin handle 'p' is instantiated

        // Actually here is the cycle but the only one port is interesting
        LV2UIPort *lp = new LV2UIAtomFloatPort(ctl, id, controller, write_function, forge); // id is 4
        p->add(lp); // Add port implementation to handle

        // Unref forge
        forge -> unbind();

        trace("Transferring ATOM FLOAT to plugin via SYNC port");
        lp->setValue(1.0f);

        // Return UI
        trace("Return handle");
        return reinterpret_cast<LV2UI_Handle>(p);
    }

x42

2015-11-20 21:09

administrator   ~0017643

It's mysterious that you can receive events on ports > 5

As for the segfault on transmission. hard to say since this is ardour without debug symbols. but it definitly looks like you're sending an invalid Atom message.

there is no property-header at the beginning and the code lacks a call to lv2_atom_forge_pop() to byte-align/pad the message.

Since there is usually only a single Atom message port, messages are usually key/value pairs.

  lv2_atom_forge_set_buffer(..., &frame,...);
  msg = (LV2_Atom*)lv2_atom_forge_object(...);
  lv2_atom_forge_property_head(&ui->forge, ui->uris.KEY, 0);
  lv2_atom_forge_float(&ui->forge, value);
  lv2_atom_forge_pop(&ui->forge, &frame);


see http://lv2plug.in/book/#_examploscope_ui_c for an example.

SadKo

2015-11-20 21:22

reporter   ~0017644

Currently i'm not using lv2_atom_forge_object function. I'm directly serializing LV2_Atom_Float into buffer so it takes 12 bytes. Will try as in example you've given.
Thanks.

SadKo

2015-11-20 22:09

reporter   ~0017646

Last edited: 2015-11-20 22:16

Looked at examploscope.
Why I'm restricted by using atom:Sequence? I want to use just more simplified structure like atom:Float. Why I have to pack it into atom:Sequence?
As I understand, it doesn't require to create object by using lv2_atom_forge_object. So that's why i've written in ttl: atom:bufferType atom:Float.

But it does not work at all.

Do I correctly understand that there is no way to use another primitive instead of atom:Sequence?

x42

2015-11-20 22:15

administrator   ~0017647

The Atom ports are not just for GUI <> plugin communication.

The host parses the format. e.g MIDI messages are also passed via an atom port and other messages may be directed at the host.

The basic protocol must be adhered to.

PS. The plugin may queue multiple events in a sequence (timestamped with sample number in realtime context). The host splits them into individual events for sending them _all_ to the UI (when the time comes to expose the GUI.

Also vice versa. since the GUI runs asynchronously, the host needs to accumulate messages for feeding them to the plugin in realtime context.

SadKo

2015-11-20 22:24

reporter   ~0017648

Okay, so the final solution is to create sequence and put LV2_Atom_Float to the sequence, then. Will try in a few days.

SadKo

2015-11-25 17:08

reporter   ~0017660

x42, thank you for help. With atom:Sequence Ardour worked stable. Added hack to my plugin that serializes all meters' values by UI request when the UI is instantiated. Then the UI simulates notify events for control ports when it receives serialized values from the plugin.

Is it possible to specify required atom buffer size dependent on current sample rate for the plugin?

x42

2015-11-25 17:57

administrator   ~0017661

great, good job in figuting this all out!


"Is it possible to specify required atom buffer size dependent on current sample rate for the plugin?"

No. http://lv2plug.in/ns/ext/resize-port/#minimumSize is in Bytes.

In practice the buffer needs to be large enough to hold information on the order of:

  messages/sample * samples/cycle * sample-rate * (audio-period / GUI-update-period)

Aim for safe max (8K samples/cycles, 192KSPS) it's usually at most a few hundred KBytes worth of buffers.
Even though all resources are precious, that part vanishes in the noise of the GBytes of sample-libs many others plugins load into RAM...

SadKo

2015-12-01 13:25

reporter   ~0017670

The next moment that I've noticed: if I have two Atom ports (UI state serialization port and bulk float vector serialization port), when I write to one of them from the plugin code, port_event comes to both in UI.

Here is the example (UI_STATE SHOULD not be triggered):

// This SHOULD NOT happen (handler for different "port_index"
[lv2ui_gtk.cpp:57] notify: received UI_STATE notification = 0x27ca020, 4, 7984
[lv2ui_gtk.cpp:60] notify: protocol (4) = http://lv2plug.in/ns/ext/atom#eventTransfer
[lv2ui_gtk.cpp:66] notify: atom.type (7) = http://lv2plug.in/ns/ext/atom#Object
[lv2ui_gtk.cpp:72] notify: obj->body.id (54) = http://my-plugin.org/plugins/lv2/phase_detector/ports#function
[lv2ui_gtk.cpp:73] notify: obj->body.otype (42) = http://my-plugin.org/types/lv2/ports#Mesh

// This is OK
[lv2ui.h:182] notify: received MESH notification = 0x27ca020, 4, 7984
[lv2ui.h:185] notify: protocol (4) = http://lv2plug.in/ns/ext/atom#eventTransfer
[lv2ui.h:191] notify: atom.type (7) = http://lv2plug.in/ns/ext/atom#Object
[lv2ui.h:197] notify: obj->body.id (54) = http://my-plugin.org/plugins/lv2/phase_detector/ports#function
[lv2ui.h:198] notify: obj->body.otype (42) = http://my-plugin.org/types/lv2/ports#Mesh
[lv2ui.h:208] notify: body->key (58) = http://my-plugin.org/types/lv2/Mesh#dimensions
[lv2ui.h:209] notify: body->value.type (27) = http://lv2plug.in/ns/ext/atom#Int
[lv2ui.h:214] notify: dimensions=2
[lv2ui.h:223] notify: body->key (57) = http://my-plugin.org/types/lv2/Mesh#items
[lv2ui.h:224] notify: body->value.type (27) = http://lv2plug.in/ns/ext/atom#Int
[lv2ui.h:229] notify: vector size=984
[lv2ui.h:241] notify: body->key (55) = http://my-plugin.org/types/lv2/Mesh#dimension0
[lv2ui.h:242] notify: body->value.type (35) = http://lv2plug.in/ns/ext/atom#Vector
[lv2ui.h:243] notify: sMesh.pUrids[0] (55) = http://my-plugin.org/types/lv2/Mesh#dimension0
[lv2ui.h:249] notify: v->body.child_type (8) = http://lv2plug.in/ns/ext/atom#Float
[lv2ui.h:250] notify: v->body.child_size = 4
[lv2ui.h:254] notify: vector items=984
[lv2ui.h:259] notify: memcpy(0x2795bc0, 0x27ca078, 3936)
[lv2ui.h:241] notify: body->key (56) = http://my-plugin.org/types/lv2/Mesh#dimension1
[lv2ui.h:242] notify: body->value.type (35) = http://lv2plug.in/ns/ext/atom#Vector
[lv2ui.h:243] notify: sMesh.pUrids[1] (56) = http://my-plugin.org/types/lv2/Mesh#dimension1
[lv2ui.h:249] notify: v->body.child_type (8) = http://lv2plug.in/ns/ext/atom#Float
[lv2ui.h:250] notify: v->body.child_size = 4
[lv2ui.h:254] notify: vector items=984
[lv2ui.h:259] notify: memcpy(0x27a87c0, 0x27caff0, 3936)
[lv2ui.h:266] notify: complete read mesh

x42

2015-12-01 13:35

administrator   ~0017671

LV2 is designed to have only one Atom port for every direction.

SadKo

2015-12-01 13:42

reporter   ~0017672

Hmm... Hypothetical question: if I want to have two MIDI ports, is it possible to distinguish what port it came from?

x42

2015-12-01 14:02

administrator   ~0017673

oops. I just double checked and my statement above was wrong:

There can be multiple Sequence ports (and also multiple midi ports). The limitation is only on the MIDI-Track itself (which records/plays back the first).

x42

2015-12-01 14:25

administrator   ~0017674

a quick check:

https://github.com/Ardour/ardour/blob/master/libs/ardour/lv2_plugin.cc#L2185 ...

line 2228 queues the message to be sent to the GUI and uses the port_index of the event.

I'll have to look further but one explanation for what you're seeing is that there's only a single _ev_buffers for the given track.

SadKo

2015-12-01 15:33

reporter   ~0017675

Here is the TTL of both ports (maybe can be useful):

        [
                a lv2:OutputPort, atom:AtomPort ;
                atom:bufferType atom:Sequence ;
                lv2:designation lv2:control ;
                lv2:index 10 ;
                lv2:symbol "ui_sync_out" ;
                lv2:name "UI Sync OUT" ;
        ] , [
                a lv2:OutputPort, atom:AtomPort ;
                atom:bufferType atom:Sequence ;
                lv2:designation lv2:control ;
                rsz:minimumSize 154368 ;
                lv2:index 11 ;
                lv2:symbol "function" ;
                lv2:name "Function" ;
        ]

x42

2015-12-01 16:00

administrator   ~0017676

As far as I know, you're the first one to write a plugin with two Atom Outputs.

I can't think of any good real world use-case for that - which does not mean there is none. Still this is probably best continued on the LV2 email list or IRC http://lv2plug.in/

SadKo

2015-12-02 09:23

reporter   ~0017678

The basic idea is simple and continues basic LADSPA/LV2 concept: store different primitives in different containers called 'ports'.

That's why I've been surprised when found out the fact that I'm being forced to use ONLY Atom:Sequence and ONLY Atom:Object when I want to communicate between UI and plugin. I can not just simple serialize a primitive object (like Atom:Float) into the chunk of memory to ensure that this chunk of memory will be transferred to the UI in the future.

Okay, Atom:Sequence is a good mechanism for queueing messages and delivering them from UI to plugin and vice verse (after some days of studying it). But there are the following questions: if one sequence is enough to communicate between UI and plugin, then:
1. Why the Atom interface is implemented in terms of LADSPA/LV2 ports? I mean why it is not implemented like other LV2 extensions (for example, the relative map/unmap interface) as single instance with non-blocking send/recv methods?
2. Why the Atom interface is split into 'IN' port and 'OUT' port? Why it can not be bidirectional and organized like a bus?

The current problem is that Ardour does not handle different LV2 ports as different LV2 ports in case of Atom ports. If Atom message delivery service is implemented like a port, the separate Atom port should work independently and similar to separate LADSPA/LV2 port, I think.

About IRC conversation: are you visiting it? I've joined but see no activity.

SadKo

2015-12-02 14:22

reporter   ~0017679

Actually, now I'm ready to confirm the BUG.

The 'function' port overrides 'ui_sync_out' port so that I can not receive UI_STATE primitive, only MESH primitive.

x42

2015-12-02 14:34

administrator   ~0017680

Please ask these questions on lv2-devel email list or lv2 IRC (I missed you there yesterday by a minute, I'm pretty much always logged but not always watch the screen; Still there are other devs which can answer your questions, too).

The short answer is: Atom sequences are not only for UI<>DSP but suitable for sample-accurate realtime use.

Anyway, the original issue (DSP not sending update of float ports to the GUI in Ardour was fixed a few weeks back in Ardour 4.4-git) and multiple Atom-ports don't usually make sense. The messages themselves already have an an identifier.

Muliple ports work if you add a 2nd MIDI-port to a MIDI track in Ardour.

SadKo

2015-12-02 15:45

reporter   ~0017681

I've asked in IRC. The members confirmed the bug and offered to refactor the plugin interface to use only one Atom port.
So the plugin's behaviour is possble but Ardour is handling it incorrect.

[18:20] <drobilla`> In a comment there you have *two* atom output ports
[18:20] <drobilla`> It's not clear to me what the other one is for
[18:20] <drobilla`> and both are designated lv2:control which doesn't really make sense
[18:20] <SadKo> Yest, the second my step was to serialize a long buffer
[18:21] <SadKo> on each plugin iteration
[18:21] <drobilla> A long buffer of what?
[18:22] <SadKo> Shortly it's a two vectors of floats
[18:22] <drobilla> For?
[18:22] <SadKo> It's an analytic function output
[18:22] <SadKo> For drawing graph
[18:23] <drobilla> What I am getting at is, merge your two output sequence ports into one and all your problems go away
[18:23] <drobilla> The general pattern is one sequence input and one sequence output for control things
[18:23] <SadKo> But that makes many following questions
[18:23] <drobilla> The only plugins that should have more are ones that process messages as "signals" like routers and mixers and so on
[18:25] <SadKo> The first question - why I can not use multiple sequences?
[18:25] <drobilla> You *can*
[18:25] <drobilla> But in your case, you shouldn't
[18:25] <SadKo> But they work wrong
[18:25] <drobilla> What you are really doing is splitting a control stream into a bunch of sequences
[18:25] <drobilla> Which is just a bad idea in general
[18:25] <drobilla> But more practically, just isn't how hosts expect things to work
[18:26] <drobilla> Plus, this way, every little thing you want to add to comm to your UI requires adding ports and breaking the plugin interface to some extent; and you have a bunch of ports that send things that make no sense to anything but your UI anyway, so there is no benefit
[18:29] <SadKo> Yes, I know that I can solve the problem by doing what you offer. Probably I'll write much more abstract serialization layer that will multiplex all my 'ports' into one Atom Sequence.
[18:29] <drobilla> Imagine the plugin is something like a physical MIDI device. You don't have a jack for note ons, and another for note offs, and another for CCs, and...
[18:30] <SadKo> The currently problem is the following: I've described two Atom ports.
[18:30] <SadKo> I write to both.
[18:31] <SadKo> But UI receives events only from the last defined port.
[18:31] <rgareus> .. but in ardour there is only ONE buffer if there's only one midi-port at the point where th plugin sits. so ardour merges the events
[18:31] <SadKo> Is it a correct behaviour of the host?
[18:31] <drobilla> Not really
[18:32] <rgareus> add a 2nd midi-port and things work. - though this is arguably a bug in ardour.
[18:32] <SadKo> And, the second moment. The event written into the second port is porte_event'ed to both atom ports of the UI.
[18:32] <drobilla> But multiple lv2:control ports in the same direction isn't either. The wording in the spec is more vague than it should be, I suppose
[18:32] <drobilla> (You can't have several *primary* control channels in the same direction)
[18:33] <drobilla> It is a bug, they should not be merged
[18:33] <drobilla> But the plugin needs to be fixed anyway
[18:34] <SadKo> So when my DSP has output atom ports 'ui_sync_out' and 'function' and writes to 'function', the UI becomes port_event to both 'ui_sync_out' and 'function'.
[18:34] <SadKo> But when it writes to 'ui_sync_out' (the first defined port), actually happens nothing.

<-- Sorry, here's connection lost -->

SadKo

2015-12-06 19:41

reporter   ~0017707

I've refactored my plugin. Now it generates atom message on each cycle. Communication between plugin and UI is OK. But when I open the configuration with 'Edit with generic controls...', I get many messages in Ardour's log:

[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI
[ERROR]: Error reading from Plugin=>UI RingBuffer
[ERROR]: Error writing from plugin to UI
[ERROR]: Error writing from plugin to UI

Issue History

Date Modified Username Field Change
2015-11-18 12:04 SadKo New Issue
2015-11-18 12:38 x42 Note Added: 0017619
2015-11-18 13:06 SadKo Note Added: 0017620
2015-11-18 15:40 x42 Note Added: 0017621
2015-11-20 10:42 SadKo Note Added: 0017633
2015-11-20 18:07 SadKo Note Added: 0017636
2015-11-20 18:39 x42 Note Added: 0017637
2015-11-20 19:24 SadKo Note Added: 0017638
2015-11-20 19:38 x42 Note Added: 0017639
2015-11-20 20:00 SadKo Note Added: 0017640
2015-11-20 20:32 SadKo Note Added: 0017641
2015-11-20 21:09 x42 Note Added: 0017643
2015-11-20 21:22 SadKo Note Added: 0017644
2015-11-20 22:09 SadKo Note Added: 0017646
2015-11-20 22:15 x42 Note Added: 0017647
2015-11-20 22:16 SadKo Note Edited: 0017646
2015-11-20 22:24 SadKo Note Added: 0017648
2015-11-25 17:08 SadKo Note Added: 0017660
2015-11-25 17:57 x42 Note Added: 0017661
2015-12-01 13:25 SadKo Note Added: 0017670
2015-12-01 13:35 x42 Note Added: 0017671
2015-12-01 13:42 SadKo Note Added: 0017672
2015-12-01 14:02 x42 Note Added: 0017673
2015-12-01 14:25 x42 Note Added: 0017674
2015-12-01 15:33 SadKo Note Added: 0017675
2015-12-01 16:00 x42 Note Added: 0017676
2015-12-02 09:23 SadKo Note Added: 0017678
2015-12-02 14:22 SadKo Note Added: 0017679
2015-12-02 14:34 x42 Note Added: 0017680
2015-12-02 15:45 SadKo Note Added: 0017681
2015-12-06 19:41 SadKo Note Added: 0017707