Plugin dev writing a sequencer?

I’d like to write a sequencer which will accept a MIDI Clock and send out both Note On/Off messages and MIDI CC messages. My first problem is receiving the steady pulse timing signal from the system and reacting to it. I started with the metronome example in the LV2 book which adds the line:

            atom:supports time:Position ;

To the ttl file for the MIDI Control port. When I added that line to my module it compiled without issue, but when I went to load the module into a pedal board it failed to load. I imagine that with that line added the Mod Device will start sending MIDI time information to the my plugin’s run() method and I’ve not added any processing as yet. But I would not have thought that my run() methods ignoring MIDI time information would mean that the plugin can’t be loaded.

I think there’s plenty of scope to augment the LV2 plugin book with little examples, a cook book, of stuff like ‘how to send a program change message’, sending a midi note message. The book is great but it only scratches the surface. There’s a lot of searching for examples of how to do things. Just not used to this yet and all the possibilities.

My recommendation would be to not go into LV2 directly but try a more high-level approach.
The way I see it, LV2 is the low-level stuff and there are utilities out there to make our life easier as developer.
I made DPF but other frameworks like JUCE can be used.
(Or other sound-specific languages like faust)

The metronome example from DPF is at https://github.com/DISTRHO/DPF/blob/main/examples/Metronome/ExamplePluginMetronome.cpp#L262

If you prefer to continue using raw LV2 that is fine too.
But then I would recommend to do testing locally on the desktop with something like Carla or jalv, until you got something that works. And then go for the embed option on the MOD unit.
Remember you are doing an LV2 plugin, not a MOD plugin. So if it works on a certain LV2 host, it should work on the next one.

Now to really answer your question: atom stuff needs to go into atom ports, not control ports.
The audio and midi file players have these definitions, for an example.


    lv2:port [
        a lv2:InputPort, atom:AtomPort ;
        atom:bufferType atom:Sequence ;
        atom:supports <http://lv2plug.in/ns/ext/time#Position> ;
        atom:supports <http://lv2plug.in/ns/ext/patch#Message> ;
        lv2:designation lv2:control ;
        lv2:index 0 ;
        lv2:symbol "lv2_events_in" ;
        lv2:name "Events Input" ;
    ] ;
5 Likes

Thanks for your recommendations. I’ve been away reading things for a month and maybe got somewhere. A Gold mine was the BSEQuencer project [1], which was really helpful in ways, but the way it works out where it is in a sequence in the plugin’s run() method was not what I expected, but I guess how else would you do it in an LV2 plugin. I was hoping to send MIDI info to other devices external to the Mod Dwarf and was thinking in terms of a MIDI Sync F8 click track at 24 times per quarter note, but this is LV2 which deals with audio.

Having said that I have a Synth which has it’s own sequencer with recorded automation so I’d have to take the BSEQuencer method, calculating in run() and calculate the MIDI Sync F8 to send out to those synths. There must be a plugin for that in the Library… Don’t see one so maybe I could write a plugin to publish, but maybe the MIDI Sync in the F8 message is a bad idea, but if you do want the Mod Dwarf to drive external synths using that method I think I need it.

[1] https://github.com/sjaehn/BSEQuencer

You can send midi in LV2.

Recently I’ve implemented midi in the DPF generator for HVCC → https://github.com/Wasted-Audio/hvcc/blob/develop/hvcc/generators/c2dpf/templates/HeavyDPF.cpp#L288

Here the handleMidiSend function is called from the run() loop via a callback (ie. when your sequencer wants to send something) and then it proceeds to, depending on the message, send the midi data using DPF writeMidiEvent

Sysex is currently not possible in DPF yet, as the message size is unknown (and potentially very big) this could block the realtime thread. Max size is now 4 midi bytes (potentially could send sysex if breaking up in 4-byte chunks? not sure)

1 Like

@falkTX thanks again for your previous answer, but I’m not sure about your advice to develop an LV2 plugin on the laptop and run it with jalv or carla. The idea that an LV2 plugin is an LV2 plugin isn’t totally true when it comes to the host you’re building for.

I’d said that I’d found BSEQuencer and was learning from that code. It’s written in C++ and I decided that I might go the same route and use C++. To help me along I found a good web page [1] which had the initial plugin written in not much more then 10 lines of C++ simply implementing the run method, inheritance taking care of the rest, ok it does nothing, but short and tidy.

So I took from that example and compiled for my laptop. Luckily I tried to compile an initial plugin for the moddwarf. I say it was lucky I tried to build for moddwarf as the example C++ plugin uses #include <lv2plugin.hpp> which is in a debian package I could install on the my Laptop. The moddwarf build system however does not include that library so I’ve got little change of getting the plugin working in C++ on the Mod Dwarf, using that library. There’s probably a different C++ library that’s available on the Mod Dwarf build system, just have to search for what it is.

[1] LV2 programming for the complete idiot

I’ll try DPF as I assume that’s supported

yeah I am sorry but the article you posted is more than 10 years old and wont really work, intentionally.
while it can give a high-level overview of LV2 things, you cannot expect it to keep working. the package you installed is no longer available in recent linux distributions because of how old (and unmaintained) it is.

While of course it’s interesting to learn from the ground up (using modern libraries and documentation, of course), DPF will help you learn plugin development from a higher level, without having to deal with low level plugin api implementations like LV2. (and bonus you also get cross-plat vst2 and vst3 compatible builds)

There’s a fair collection of opensource DPF plugins you can inspect for inspiration.

I agree, but you mentioned two things together, ‘modern libraries and documentation’ which is maybe important when you’re learning an ecosystem. I know I want a plugin for a Mod Devices product, without that plugin I have little use for the moddwarf. So whilst it’s all well can good to be able to write a plugin for my laptop ultimately that’s not where i want the plugin. So I want to be able to build a plugin for the moddwarf as the target platform. It will be handy to be able to develop it on the Laptop and be able to test it on that platform but ultimately it has to run on the mod devices platform.

So given a modern library and documentation for DPF all I want as a starting point is to be able to write a plugin that does nothing and deploy it to the moddwarf. After hours of searching and cutting and pasting I was unable to create, or find, a Makefile which would find the needed libraries during build. So the very first line of code, an include of the dpf header files is a block.

That’s my problem either I’m using the wrong search terms or can’t operate the Mod Devices Wiki to find good examples. So whilst a modern library is great when you know how to use it I can’t use it. In which case ground up with straight C is the way to go. I can build a plugin for the ModDwarf with straight C. But DPF looks great and if I was developing for a Laptop it’d be an option,

Developing for MOD or Desktop has the same 99% of the base work. The main difference is how the GUI is done, but this is not C or C++ code anyway.

We want to make documentation better, but 1.11 bugfixing got most of the time so far.

For DPF most people will just copy existing plugins and go from there, for example https://github.com/DISTRHO/Mini-Series

Maybe cmake is an option? The develop branch of DPF supports that now, should be as simple as:

cmake_minimum_required(VERSION 3.15)
project(MyCompressor VERSION 0.0.0)

add_subdirectory(DPF)

dpf_add_plugin(MyCompressor
  TARGETS jack lv2 vst2 vst3
  FILES_DSP
      MyCompressorPlugin.cpp
  FILES_UI
      MyCompressorUI.cpp)

target_include_directories(
  MyCompressor PUBLIC ".")
1 Like

One of the advantages of the ModDwarf is that I don’t really have to worry about a GUI. It’s more or less a pedal, so I’ll only use the GUI in the web browser to configure what the buttons do, and connections.

Thanks for Mini-Series some useful examples and I think I had a realisation looking at your file structure, and possibly why I was having so much difficulty trying to create a Makefile for a basic DPF project.

I had the idea to copy one of your examples into moddwarf and build it, I know it builds:

$ mkdir …ModDevices/mod-plugin-builder/plugins/package/3BandEQ
$ cp -r 3BandEQ …ModDevices/mod-plugin-builder/plugins/package/3BandEQ

So then create a buildroot makefile

$ touch …ModDevices/mod-plugin-builder/plugins/package/3BandEQ/3BandEQ.mk

It was only when I get into your make file for the 3BandEQ to see what changes have to be made that it provokes a few questions…

The example that I used to build a basic C++ plugin previously, which you said was ten year old and not really supported. That came in as a debian package with include files copied to the standard locations in the file system and the library to link against. I was assuming that DPF was a similar story and that it was bundled in the ModDwarf build system like ffmpeg or whatever libraries. But looking at your examples you include DPF in the file structure and build it as part of the 3BandEQ project.

So in the cmake example you have me you have:

add_subdirectory(DPF)

If that’s the case it kinda clears up a few assumptions I was making.

3 Likes

So after a bit of reading up on CMake I decided I’d have another go with DPF to write an LV2 sequencer. Started with the first example from [1], but that wasn’t as clean as I’d hoped. I assume it’s my CMake, but it’s very close to the example earlier in this thread. I’ve got a CMakeLists.txt of:

cmake_minimum_required(VERSION 3.15)
project(Sequencer VERSION 0.0.0)

add_subdirectory(DPF)

dpf_add_plugin(Sequencer
  TARGETS lv2
  FILES_DSP
      sequencer.cpp)

target_include_directories(
  Sequencer PUBLIC ".")

I’ve just removed the GUI elements as I’m not really interested in the GUI at the moment. But basically I’ve pretty much the same CMake as above but the build process can’t find the only Include file I use:

sequencer.cpp:1:10: fatal error: DistrhoPluginInternal.hpp: No such file or directory

That file is down the DPF directory:

/DPF/distrho/src/DistrhoPluginInternal.hpp

I’ve got everything at the same level in the directory structure, but assume that the CMake adding the DPF directory would sort out its includes?

[1] DISTRHO Plugin Framework: DISTRHO Plugin Framework

I am not too versed in cmake, but can ask…

@jpcima since you did the cmake part, do you know how to proceed here?

It is not permitted to include internal headers. The cmake target does not make these header paths public. It should be fine to use DistrhoPlugin.hpp instead.

2 Likes

Thank you that cleared that error. Hitting another problem now, but that’s to do with the ttl file, which I assume is auto generated by DPF. To be fair I’m not telling it a great deal about my plugin, the bare minimum as defined in [1].

/bin/sh: 1: /home/john/mod-workdir/moddwarf/build/sequencer-1/lv2_ttl_generator: Exec format error

#define 	DISTRHO_PLUGIN_NAME   "sequencer"
#define 	DISTRHO_PLUGIN_NUM_INPUTS   0
#define 	DISTRHO_PLUGIN_NUM_OUTPUTS   0
#define 	DISTRHO_PLUGIN_URI   "com.example/sequencer"
#define 	DISTRHO_PLUGIN_WANT_MIDI_OUTPUT   1

[1] DISTRHO Plugin Framework: Plugin Macros

Ok that is a good step.
You are not doing anything wrong, it is just that now comes a tricky part…

DPF builds a tool that is able to generate ttl from its compiled code.
You are building for the Dwarf, so the binaries generated are 64bit ARM ones (aka aarch64).
Since you are likely running a regular Intel/AMD 64bit PC, not ARM, your system is unable to run the binaries generated.

The good news is that, if you are running Debian (or any Debian based distribution like Mint, PopOS or Ubuntu) we can setup the system to be able to run ARM binaries directly.
Runs very slow, but for such small tools as the ttl generator from DPF it doesnt really matter.
You will need a setup similar to the one used in the mod-plugin-builder Dockerfile, starting at https://github.com/moddevices/mod-plugin-builder/blob/master/docker/Dockerfile#L8

Basically enabling arm64, setting up /etc/apt/sources.list so it can support arm64 deb packages, and then install shared libraries in arm64 architecture.

1 Like