Morph Plugin for Controlling Automation

Hey everyone! First post here! I made a Lua plugin to help me control automation lanes, and I uploaded it to github so all can use it. This is a copy/paste of the readme. Would love to hear your thoughts! I’d especially love to hear if anything like this is on the roadmap for Ardour itself.

Morph Plugin

General Morph Plugin for Controlling Automation and Interpolating Between Values

This is a general plugin that allows one automation lane to control multiple other automation lanes. For each target lane, it can store up to ten automation values and can interpolate between them using the main controller’s slider.

Before describing how it all works, here is a quick demo video of constructing a multiband delay and controlling it with Morph plugins. An original signal (simple guitar chords, far left track) is split into eight tracks, each one containing a Morph Locator and an ACE Delay plugin. Morph Controllers then target each of the eight ACE Delays in order to control their delay times and feedback strengths. The first half of the video shows the way that parameters are linked, and the second half contains an audio example of the multiband delay in practice. (Audio starts at 1:20 and is loud.) Note that everything here could be done using normal automation without the Morphs, but the Morphs make the desired values much more convenient to hit and reduce the 16 automatable parameters down to two. With only two parameters, they can be mapped easily to a midi controller and played live.

Basic Usage / Architecture Overview

As it stands currently, the “plugin” consists of three separate pieces of lua code. Two are dsp processors and the third is a session script. The dsp processors are simply containers of values and do no processing. All of the processing is actually in the session script.

  1. morph_locator.lua is an incredibly simple dsp processor that has exactly one parameter: locator_ID. This plugin needs to be placed in a track immediately before the desired target plugin. Manually ensure that every Morph Locator has a unique locator_ID set. (But note that multiple Morph Controllers can target the same Morph Locator.)

  2. morph_controller.lua has a large number of parameters.

    • The very first parameter is the controller value that slides between 0 and 1.
    • The remaining 14 parameters are replicated once for each of 8 targets.
    • The __target_plugin_id parameter should be set to the same value as the locator_ID of a Morph Locator in order to control the target.
    • The parameter __target_nth_param determines which of the target’s parameters is to be controlled.
    • The __target_enabled parameter determines whether this target’s automation is controlled or not.
    • The remaining parameters store up to 10 values for the target automation lane, and the target_control_point_count parameter dictates how many of those 10 values are interpolated between as the main controller slides between 0 and 1.
  3. morph_lane_linker.lua is a session script that, upon loading, finds all Morph Locators, all plugins immediately following Morph Locators, and all Morph Controllers, and then uses the configurations of the Morph Locators and Controllers to write automation values into the desired targets. This script should need no intervention except to be loaded. (And it will need to be unloaded and then reloaded if something major changes, such as moving a Morph Locator.) For convenience, upon loading it prints out all automatable parameters for any target plugins immediately following Morph Locator plugins. Check the output log for messages.

“Installation”

Copy the three Lua scripts into the appropriate folder. For me on linux, that folder is ~/.config/ardour7/scripts

Afterwards, launching Ardour should show Morph Locator and Morph Controller in the list of plugins that can be added to a track. Add and configure those as necessary. To run the session script, go to the Edit menu in the toolbar, hover Lua Scripts, select Script Manager. In the window that appears, click on the Session tab, then Load the Morph Lane Linker script.

Thoughts, Discussions, Why

This type of functionality would be amazing to have built into Ardour directly. I saw online a recent interview with Robin where he teased (teased is too strong a word even) that automation control might possibly be starting development in a couple years. And as of late, I have been using Vital and Surge XT synths in my own music making, both of which have extremely strong modulation capabilities, so much so that I find myself trying to do more and more work in the synths themselves rather than in the DAW. Vital’s mod remap is especially lovely.

A full deep-dive into automatable automation would definitely be a ton of work for Ardour or any DAW. The Morph plugin here provides a very simple/crude approximation of some of those features.

The Locators are needed because Ardour’s IDs for plugins seems to change at random times.

Sometimes the session script does not appear to be running unless the transport is acting. Unsure why.

This does not play well with the has-been-modified asterisk next to the filename in the titlebar indicating that the project has changed. Notice in the video that the two Morph Controllers’ controllers have their automation modes set to Play (making their sliders gray in the mixer strips) but the ACE Delays are set to Manual (and thus their sliders in the mixer strips remain blue as though they could be adjusted).

Is this zipper friendly? No clue! Close enough for my purposes. What’s the update frequency? Also no clue!

What am I hearing in the demo? Raw direct input from my guitar as I strummed some chords is processed by a make-shift multiband delay. Based on settings, the multiband delay can delay specific frequency bands more than others. The spectrogram on the original signal shows all of the frequencies arriving simultaneously on every chord strum, but the output signal’s spectrogram on the far right shows that different frequencies get spread out. Audibly, the first four chord strikes are unmodified, and the effect begins on the fifth chord. For the first few effected chords, the low frequency parts are delayed less than the high frequency parts, so the bass swells and precedes the sharper snap of the transient. Later on in the demo, the delay times are varied, and feedback in the whole setup makes everything even more chaotic.

Huge Drawback to Current Solution

Because the main component is a “session” script, it requires a session in order to operate. As far as I can tell, that means that morph_lane_linker.lua does not run when exporting a project, regardless of whether freewheeling or real-time exporting. The solution for now is to record / bounce the audio into a track and then use the recorded region instead of the automatable setup.

TODO and Immediate Future

  • linear interpolation vs discrete steps
  • If you have better names for anything here, let me know. Nothing is set in stone.

Distant Future

  • Modify morph_lane_linker.lua to constantly watch the project and auto-configure itself as necessary instead of relying on unload/reload
  • Is there a way to do automated / unit tests on Lua code? Would it be possible to get a headless Ardour session that could run all of the components such that I could automate creating a track, creating a plugin, loading the Morph stuff, changing values, and ensuring that the appropriate automatables have the correct values?
  • Is it possible for a Lua DSP processor (or any processor, for that matter) to access the Session and Ardour.LuaAPI objects in the dsp_run method? If so, then all of the morph_lane_linker code could be moved into the morph_processor to eliminate the session script.

Even Distanter Future

  • GUI
  • Can this kind of functionality be built into Ardour directly?

Inspirations

FL’s Multiband Delay (with Morph knob at 4:30) – Aaaaah, I’m a new user and can’t add too many posts. Search for these on youtube.
Morph EQ (a whole plugin completely unrelated to the work here) …

Closing Thoughts

This is currently a bit awkward to use, but it’s good enough for me personally. I might modify it a bit as I find more capabilities that need to be covered, but if you use it and have suggestions, let me know!

The Lua stuff was surprisingly hard to figure out, but the massive number of scripts in Ardour’s github repo were incredibly, incredibly helpful.

I am really enjoying my time with Ardour! Music is fun.

Here’s a link to the repo where you can find the three lua components. GitHub - rrastgoufard/Morph_Ardour: General Morph Plugin for Controlling Automation and Interpolating Between Values

6 Likes

Powerful!!! I haven’t tried it yet - but already like it! Thank you! Need to test definitely ///
A concomitant question: what is that MB split plugin in the source sound track?
Good luck and creative mood!

1 Like

AA… Sorry I’ve just seen. It’s a send to the bus with 8 channel crossover.

Now I’ve got the result - everything works, as you described:
morth_noize_mak3r_cutoff_reso
I put the Controller and Locator in to the same midi track with the synth plugin.
(Ardour 7.2, NoizeMak3r synth, cutoff-increse&reso-decrease)
Interesting possibilities. Thanks again!

1 Like

That’s fantastic! I’m really pleased that it worked for you. :slight_smile:

Look at those filter knobs go!

Hi, Mr. rastin!
Continue the exploration…
The Controller is placed in the track with the Helm and the ACE Filter.
There are:
the Locator with ID:1 - before the Helm ,
the Locator with ID:2 - before the ACE Filter.
Everything works well!
Also It looks I’ve understood how the 10 targets are working.
morth_helm_ACEfilter_cutoff_reso
Superr!!

I don’t know how the generic UI is working (sorry if this is stupid) - just an idea - if the names: “target1_control1” could be short (for example) “t1_c1” - may be this could take not so much area in the generic UI → this could give the possibility to place more columns in the Controller UI visible area.

One little difficulty I’ve faced - how to find the controller number in the plugin with a large quantity of controllers (Helm for example has 139):
helm_controllers

ps:
:twisted_rightwards_arrows:one automation line <-> everything is moving :repeat:
Big thanks!))

1 Like

Using shortened names is brilliant. I just pushed an updated version to the github repo. Here’s a screenshot of all of the parameters fitting onto my screen at once without needing a scrollbar.

Note that, unfortunately, because these are lua plugins, there is no way that I know of to copy your settings from an existing controller over to the new one which is treated as a different plugin entirely. Fortunately, your old ones will remain working and loaded with no changes in that session and you can have both at the same time.

1 Like

Finding the right parameter is difficult indeed! If you check the session log, you should find the answer you’re looking for. When you add the morph_lane_linker.lua script, it prints a lot of debug information there, including a numbered list of all parameters for all plugins in the session, their minimum values, maximum values, and whether or not the parameters are logarithmic.

1 Like

ogo:)) I didn’t know that response&fixing could be so fast))

  • I haven’t done super morphing projects yet.))

the result from my testing (your fixing&answers):
new_ui_surge_log_numbers

  • I’ve found the SurgeXT’s cutoff in the Log.
    What else must I want?? Super-duper! Big big thanks&LUCK! :heavy_check_mark:
1 Like

Nice work and a great concept.

I’m an Ardour-Lua hobbyist too and dabble with using Lua plugins for ‘non-plugin’ things. Something I read, but not sure where, is that in dsp_run()/dsp_runmap() the in and out buffers are sometimes the same buffer (“inline”) in which case whatever audio/midi is ‘coming in’ to the plugin is automatically also ‘going out’, so if this function does nothing (as yours is) then that’s fine; signal ‘passes through’ the plugin. BUT if the buffers aren’t the same/inline then input data is not auto-written to the output buffer, so the output of the plugin will be nothing (silence); the strip’s audio/midi is blocked by your plugin.

I don’t know if I’m explaining that well or correctly but the upshot is this is why some of the sample scripts have this kind of thing…

function dsp_configure (ins, outs)
  n_audio = outs:n_audio()
end

function dsp_run (ins, outs, n_samples)

  -- direct transfer from ins to outs
  for c = 1, n_audio do
    if ins[c] ~= outs[c] then
      ARDOUR.DSP.copy_vector (outs[c]:offset(0), ins[c]:offset(0), n_samples)
    end
  end
end

I.e. For each pair of in and out buffers, if they are not the same buffer then copy data from in to out. If they are the same then do nothing.

Note that this code assumes the number of input buffers and output buffers is the same.

Thanks for the feedback! I’m a lua newbie and this is my first time poking at Ardour’s scripting.

I added the following code in the dsp_run of morph_locator.lua and morph_controller.lua as you suggested. Changes have been pushed to the repo.

function dsp_run(ins, outs, n_samples)
  -- process all channels
  for c = 1, #ins do
    -- when not processing in-place, copy the data from input to output first
    if ins[c] ~= outs[c] then
      ARDOUR.DSP.copy_vector (outs[c], ins[c], n_samples)
    end
  end
end

Also, I am assuming channel counts are the same because of the -1, -1 in dsp_ioconfig.

function dsp_ioconfig()
  return {
    {audio_in = -1, audio_out = -1},
  }
end

I honestly have no clue when processing would happen in-place vs otherwise. If you do happen to come across any documentation about it, please send it my way! For the moment, I tried the code both without anything in dsp_run (as it was originally) as well as with the channel copying (as you suggested) and noticed no difference in my usual play/record/export uses of Ardour. Either way, the repo contains the changes you suggested.

Thanks again! I appreciate all the feedback.

:alien: Insidious Morph Linker tries to control everything:

This reminds me SurgeXT with its Macro(s) and LFO(s) sliders)/// - but this time the DAW Ardour is at the place of the surge UI.

An idea (again):
May be it’s possible to make another kind of plugin with the LFO sliders (on the place of the main controller of your existing plugin):


(just an idea - I can understand that this may become a big development task - sorry - just couldn’t resist not to voice:))

Also I’ve tested 3 Controllers simultaneously in one track - they can control their locators without interfering with each other (well)(!!):
3_controllers

1 Like

I love the enthusiasm! :smiley: The Surge XT comparison makes me really happy, considering it was something I had in mind when I was making this.

Regarding LFO, I was thinking about it loosely, and I might put something together. I wouldn’t do it as a modification of morph_controller as your mockup suggests, but instead I was thinking of having the LFO be a single lane that can target (using the existing morph_locator) the controller of a morph_controller. Not sure if I will do it though because I have a lot of questions about the start/stop cycle, phase, tempo sync, free-running, etc. Lemme think about it for a few days. (Your idea is reasonable, and you are correct that the functionality is very related to the existing morph_lane_linker.)

Regarding the 3 controllers, I also ended up liking the pattern of putting all of the morph_controllers into a dedicated lane.

Did you know that you can use morph locators to target morph controller parameters?? :sunglasses:

1 Like

Yes, yes!! I don’t know coding… I felt that LUA power is existing, but now I know (with your commits, rastin))))

I try to think about it carefully that the Morph Linker could not capture my brain. :space_invader:
Thank you!))

Alright cooltehno, you’ve inspired me. Morph LFO is in the repo now.

Lemme know how it feels if you get a chance to play around with it!

1 Like

That’s great! I don’t know what to do with such crazy possibilities stuff yet// :fire: Need much experimenting.
Just some first steps, where I began to forget what things control each other (careful//video with a sound):

Anyway some things came to me during exploration:

  • I need to make a bunch of plugins LFO-Locator-Controller every time when I need to add LFO (and also make some adjustments about ID(s) ). So if the LFO could be inside the Controller plugin that you’ve already made - this could be much faster and let avoid mess with ID(s) and Locators. This could be some sort of Controller that you’ve made but with LFO possibility.
  • Or may be this could be super to have an universal Controller plugin which has two modes: an “automatable slider” - mode and “LFO”-mode.

But again my brain can’t focus what to want from such possibilities spectrum!! I’ve forget about music during this exploration. :)) Need further comprehension… Thanks for a cascade of brilliant things!

One another thing about MIDI channel Locator inserting:
locator_pin
The Locator plugin breaks the MIDI connection to the synth plugin - so I need manually restore the connection (I didn’t noticed this before until I try to play MIDI notes in the track). This is not a problem, but probably may make some confusion for newbies I think :))

1 Like

You’re totally right – having the LFO be a standalone plugin makes a mess of Locators and Controllers. As you suggested, I rolled the LFO functionality into Morph Controller. The LFO feature modifies the Controller itself and does not interact with Locators anymore.

The new updates are in the repo. I liked your universal Controller suggestion!

Thanks for the heads up about the plugins breaking midi pass through. I tried a few alternatives in the plugin code, but none of them worked like I wanted… :frowning: Note that this affects both the Locators and the Controllers.

Setting midi_in = 1, midi_out = 1 made things functionally work, but then I didn’t like how the channel strips would look when the plugin introduces a disconnected midi input followed by a disconnected midi output.

2023_01_14_midi1_red

Setting midi_in = -1, midi_out = -1 caused Ardour to crash when using Flexible IO. (I was hoping this would work like audio_in = -1, audio_out = -1 such that it doesn’t care how many audio ins/outs there are as long as the number is the same.)

Setting midi_in = 0, midi_out = 0 is the current behavior. There should be no midi blocking if the track is in Strict IO, and if it is in Flexible IO, then you must do manual routing as in your screencast.

I don’t know of a great solution… But maybe we should just go with midi_in=1, midi_out=1 despite its aesthetic.

Do you happen to know if Strict IO is the default in Ardour on a fresh install?


EDIT: I changed my mind and decided to use midi_in=1, midi_out=1 so that it always works. This version is in the repo now. It actually looks really good in any midi track, either before the synth (where there is no audio yet) or after the synth (where there is both audio and midi). It looks a little awkward in audio-only tracks.

Also, I noticed that Ardour crashes when deleting a Controller that is actively using its LFO. unsure if there’s an easy fix for this…

Yes, that assumption is correct.

Me neither. It always seems to be inline for me. Perhaps it depends on the device?

From the Ardour Manual:

  • The script has access to the current session via the global variable Session, but access to the session methods are limited to realtime safe functions

DSP and Session scripts need to be fast because they’re doing all of their processing in that tiny window of ‘buffer time’, hundreds/thousands of times every second. Some Session object methods are just too slow for that, so aren’t available - I don’t know which ones though.

About the tight processing loop, that makes a lot of sense. Thanks for pointing that out in the manual.

Do you have a good way of debugging things that go into the dsp_run method? I’m not sure if print or crash messages work at all in there… I ended up relying more on the “session” script where these things worked rather than in the “dsp” plugin just so I could inspect what was going on.

The only thing giving me hope about using something Session-like in the dsp_run is this example from the Ardour lua scripts.

It’s a dsp plugin that uses routes, nth_plugin, casts going to_insert, etc, all of which I need, and all in the dsp_run method. If all of that is safe to use in a tight-loop, then from this example alone, I am guessing that the only thing I’d be missing would be something equivalent to ARDOUR.LuaAPI.get_processor_param and set_processor_param. It would be absolutely lovely to move everything from the morph_lane_linker “session” script directly into the morph_controller “dsp” plugin.

(I’m concerned about overworking the main thread that session plugins like morph_lane_linker run in. From the beginning I wanted to distribute the work somehow into those dsp processors, but due to how opaque it was and how little experience I had, I shied away from it.)

Universal Controller - super good!! Grand thank you rastin!
Everything works as expected!
Playing with tuning Helm and morphing the ACE EQ:

Look at this EQ-moving elegance!
During the rhythm playing - the thought came may be to make possible synchronize the LFO with the DAW.

About the MIDI connections - also works perfect: I inserted Helm first and after locator(above the Helm)&Controller - no need to manually connect MIDI!
locators_controller_chain

Flexible/Strict I/O - works the same good for me!
Of coarse almost every modern synth has LFO or Macro modulation, but your locators can modulate every the rest plugins in Ardour (and simultaneously!).
Thanks again!

1 Like