Lua arpeggiator plugin anyone?

Thanks very much for sharing such an interesting tool!

Impressive! You’re clearing having fun with it.

You bet. :slight_smile: I love Lua.

Robin, is there a way to include some factory presets in a Lua dsp plugin? We might collect some and include them for instructional purposes.

No, not currently. And yet again you provide motivation for something that has been on my TODO list for a while. Although this time it will take a bit more time to add.

User-presets for Lua DSP plugins are saved in the user config dir: ~/.config/ardour7/presets/

Yes, I understand, but they have names like “lua-7a10f7ef704f771d0e1ab13de2fd0d997442dade”. Seems to be the SHA1 hash of the plugin text. Now I could include the files under a descriptive name in the repo and have the installable files with the proper hashes in the filenames generated by a Makefile. But it wouldn’t be the most user-friendly procedure.

So a way to include factory presets in the plugin seems preferable for ease of use. I understand, though, if that’s not something that’s high on your priority list.

Here’s another little demo session that I did today while debugging a glitch in the arpeggiator (now fixed). It shows what can be done with a harmonic progression from the Ardour MIDI clip library, using simple_arp for the drums and barlow_arp for the harp and bass, gmsynth as synthesizer on all tracks. The pattern of the harp changes through automation to give some variation. The bass uses the same MIDI clip, just moving in quarter notes.

Sounds pretty nice, doesn’t it? Of course most of that is due to the harmonic progression which is really beautiful. But without the arpeggiators it wouldn’t sound nearly as good. :wink:

Here’s a quick export from Ardour for those who can’t play the session:

Robin, I have another question concerning the Lua interface. In the midiout table, must the time stamps in the events always be in ascending order, or is that handled automatically in the interface after returning from dsp_run?

I’m asking because there may be situations in the arpeggiator where it passes through some cc messages or pitch bends (where I simply copy the events from midiin to midiout), and then goes on to generate an arpeggiator note, all in the same cycle. In that case the time field of the generated note may actually be before the time fields of (some of the) copied events.

If that’s a problem then I might have to sort the midiout table by time stamps before returning from dsp_run.

Also, I’m fighting an issue where I occasionally get hanging notes in the arpeggiator while playing it live through the MIDI keyboard in a loop. It’s hard to reproduce this issue, but looking at the log it seems that sometimes a note-off received at the end of a loop gets delivered much too late. This then causes the note to stick around in the chord memory of the arpeggiator. Once I notice this and stop transport, the note-off gets delivered, and scrolling back in the log I then see the corresponding note-on much earlier.

This is driving me nuts, because it’s so hard to reproduce. It only happens when playing live, never (afaict) when using pre-recorded MIDI data from the track. I don’t think that it’s a faulty MIDI keyboard either – the one I’m using here is an AKAI MPK miniplus, still brand-new, and it would be really weird if stopping transport in Ardour would somehow magically make that hanging note-off appear. :wink:

So might there be situations in live MIDI input, where a note-off arrives right at the end of the loop range, then gets time-stamped before wrapping around and queued up to be delivered at a much too late sample time? I’m obviously speculating there, but I’ve tried to debug this issue for the better part of two days now, and I’m running out of ideas.

FWIW, here’s the relevant part of the log from a run of the arpeggiator.

2023-06-28T17:31:22 [INFO]: LuaProc: note on 60 115 ch 0
2023-06-28T17:31:22 [INFO]: LuaProc: pattern: 48 60 72 60
2023-06-28T17:31:22 [INFO]: LuaProc: pattern: 72 60 48
2023-06-28T17:31:22 [INFO]: LuaProc: pattern: 48 60 72
2023-06-28T17:31:22 [INFO]: LuaProc: 8|3|640 - 30.3333 [179] - 4/4 - 120 bpm
2023-06-28T17:31:22 [INFO]: LuaProc: 8|3|1280 - 30.6667 [243] - 4/4 - 120 bpm
2023-06-28T17:31:22 [INFO]: LuaProc: 8|4|0 - 31 [48] - 4/4 - 120 bpm
2023-06-28T17:31:22 [INFO]: LuaProc: 8|4|640 - 31.3333 [109] - 4/4 - 120 bpm
2023-06-28T17:31:23 [INFO]: LuaProc: 8|4|1280 - 31.6667 [179] - 4/4 - 120 bpm
2023-06-28T17:31:23 [INFO]: LuaProc: pattern: 72 60 48
2023-06-28T17:31:23 [INFO]: LuaProc: 1|1|0 - 0 [0] - 4/4 - 120 bpm
2023-06-28T17:31:23 [INFO]: LuaProc: 1|1|640 - 0.333333 [48] - 4/4 - 120 bpm
2023-06-28T17:31:23 [INFO]: LuaProc: 1|1|1280 - 0.666667 [109] - 4/4 - 120 bpm
2023-06-28T17:31:23 [INFO]: LuaProc: 1|2|0 - 1 [179] - 4/4 - 120 bpm
2023-06-28T17:31:23 [INFO]: LuaProc: 1|2|640 - 1.33333 [243] - 4/4 - 120 bpm
2023-06-28T17:31:24 [INFO]: LuaProc: 1|2|1280 - 1.66667 [48] - 4/4 - 120 bpm
2023-06-28T17:31:24 [INFO]: LuaProc: 1|3|0 - 2 [109] - 4/4 - 120 bpm
2023-06-28T17:31:24 [INFO]: LuaProc: 1|3|640 - 2.33333 [179] - 4/4 - 120 bpm
2023-06-28T17:31:24 [INFO]: LuaProc: 1|3|1280 - 2.66667 [243] - 4/4 - 120 bpm
2023-06-28T17:31:24 [INFO]: LuaProc: 1|4|0 - 3 [48] - 4/4 - 120 bpm
2023-06-28T17:31:24 [INFO]: LuaProc: 1|4|640 - 3.33333 [109] - 4/4 - 120 bpm
2023-06-28T17:31:25 [INFO]: LuaProc: 1|4|1280 - 3.66667 [179] - 4/4 - 120 bpm
2023-06-28T17:31:25 [INFO]: LuaProc: pattern: 48 60 72 60
2023-06-28T17:31:25 [INFO]: LuaProc: 2|1|0 - 4 [0] - 4/4 - 120 bpm
2023-06-28T17:31:25 [INFO]: LuaProc: note on 52 82 ch 0
2023-06-28T17:31:25 [INFO]: LuaProc: pattern: 40 48 52 60 64 72 64 60 52 48
2023-06-28T17:31:25 [INFO]: LuaProc: note on 57 71 ch 0
2023-06-28T17:31:25 [INFO]: LuaProc: pattern: 40 45 48 52 57 60 64 69 72 69 64 60 57 52 48 45
2023-06-28T17:31:25 [INFO]: LuaProc: note on 48 56 ch 0
2023-06-28T17:31:25 [INFO]: LuaProc: pattern: 36 40 45 48 48 52 57 60 60 64 69 72 69 64 60 60 57 52 48 48 45 40
2023-06-28T17:31:25 [INFO]: LuaProc: 2|1|640 - 4.33333 [48] - 4/4 - 120 bpm
2023-06-28T17:31:25 [INFO]: LuaProc: 2|1|1280 - 4.66667 [109] - 4/4 - 120 bpm
2023-06-28T17:31:25 [INFO]: LuaProc: note off 52 0
2023-06-28T17:31:25 [INFO]: LuaProc: note off 57 0
2023-06-28T17:31:25 [INFO]: LuaProc: note off 48 0
2023-06-28T17:31:25 [INFO]: LuaProc: 2|2|0 - 5 [179] - 4/4 - 120 bpm
2023-06-28T17:31:25 [INFO]: LuaProc: 2|2|640 - 5.33333 [243] - 4/4 - 120 bpm
2023-06-28T17:31:26 [INFO]: LuaProc: 2|2|1280 - 5.66667 [48] - 4/4 - 120 bpm
2023-06-28T17:31:26 [INFO]: LuaProc: 2|3|0 - 6 [109] - 4/4 - 120 bpm
2023-06-28T17:31:26 [INFO]: LuaProc: 2|3|640 - 6.33333 [179] - 4/4 - 120 bpm
2023-06-28T17:31:26 [INFO]: LuaProc: 2|3|1280 - 6.66667 [243] - 4/4 - 120 bpm
2023-06-28T17:31:26 [INFO]: LuaProc: 2|4|0 - 7 [48] - 4/4 - 120 bpm
2023-06-28T17:31:26 [INFO]: LuaProc: 2|4|640 - 7.33333 [109] - 4/4 - 120 bpm
2023-06-28T17:31:27 [INFO]: LuaProc: 2|4|1280 - 7.66667 [179] - 4/4 - 120 bpm
2023-06-28T17:31:27 [INFO]: LuaProc: note on 56 58 ch 0
2023-06-28T17:31:27 [INFO]: LuaProc: note on 51 64 ch 0
2023-06-28T17:31:27 [INFO]: LuaProc: pattern: 36 39 40 44 45 48 48 51 52 56 57 60 60 63 64 68 69 72 69 68 64 63 60 60 57 56 52 51 48 48 45 44 40 39
2023-06-28T17:31:27 [INFO]: LuaProc: note on 48 75 ch 0
2023-06-28T17:31:27 [INFO]: LuaProc: pattern: 40 45 44 39 36 52 57 56 51 48 48 72 64 69 68 63 60 60
2023-06-28T17:31:27 [INFO]: LuaProc: 3|1|0 - 8 [0] - 4/4 - 120 bpm
2023-06-28T17:31:27 [INFO]: LuaProc: 3|1|640 - 8.33333 [48] - 4/4 - 120 bpm
2023-06-28T17:31:27 [INFO]: LuaProc: note off 51 0
2023-06-28T17:31:27 [INFO]: LuaProc: note off 48 0
2023-06-28T17:31:27 [INFO]: LuaProc: note off 56 0
2023-06-28T17:31:27 [INFO]: LuaProc: 3|1|1280 - 8.66667 [109] - 4/4 - 120 bpm
2023-06-28T17:31:27 [INFO]: LuaProc: 3|2|0 - 9 [179] - 4/4 - 120 bpm
2023-06-28T17:31:27 [INFO]: LuaProc: 3|2|640 - 9.33333 [243] - 4/4 - 120 bpm
2023-06-28T17:31:28 [INFO]: LuaProc: 3|2|1280 - 9.66667 [48] - 4/4 - 120 bpm
2023-06-28T17:31:28 [INFO]: LuaProc: 3|3|0 - 10 [109] - 4/4 - 120 bpm
2023-06-28T17:31:28 [INFO]: LuaProc: 3|3|640 - 10.3333 [179] - 4/4 - 120 bpm
2023-06-28T17:31:28 [INFO]: LuaProc: 3|3|1280 - 10.6667 [243] - 4/4 - 120 bpm
2023-06-28T17:31:28 [INFO]: LuaProc: 3|4|0 - 11 [48] - 4/4 - 120 bpm
2023-06-28T17:31:28 [INFO]: LuaProc: 3|4|640 - 11.3333 [109] - 4/4 - 120 bpm
2023-06-28T17:31:29 [INFO]: LuaProc: 3|4|1280 - 11.6667 [179] - 4/4 - 120 bpm
2023-06-28T17:31:29 [INFO]: LuaProc: pattern: 36 39 40 44 45 48 48 51 52 56 57 60 60 63 64 68 69 72 72 69 68 64 63 60 60 57 56 52 51 48 48 45 44 40 39
2023-06-28T17:31:29 [INFO]: LuaProc: pattern: 36 39 40 44 45 48 48 51 52 56 57 60 60 63 64 68 69 72 69 68 64 63 60 60 57 56 52 51 48 48 45 44 40 39
2023-06-28T17:31:29 [INFO]: LuaProc: 4|1|0 - 12 [0] - 4/4 - 120 bpm
2023-06-28T17:31:29 [INFO]: LuaProc: 4|1|640 - 12.3333 [48] - 4/4 - 120 bpm
2023-06-28T17:31:29 [INFO]: LuaProc: 4|1|1280 - 12.6667 [109] - 4/4 - 120 bpm
2023-06-28T17:31:29 [INFO]: LuaProc: 4|2|0 - 13 [179] - 4/4 - 120 bpm
2023-06-28T17:31:29 [INFO]: LuaProc: 4|2|640 - 13.3333 [243] - 4/4 - 120 bpm
2023-06-28T17:31:30 [INFO]: LuaProc: 4|2|1280 - 13.6667 [48] - 4/4 - 120 bpm
2023-06-28T17:31:30 [INFO]: LuaProc: 4|3|0 - 14 [109] - 4/4 - 120 bpm
2023-06-28T17:31:30 [INFO]: LuaProc: 4|3|640 - 14.3333 [179] - 4/4 - 120 bpm
2023-06-28T17:31:30 [INFO]: LuaProc: 4|3|1280 - 14.6667 [243] - 4/4 - 120 bpm
2023-06-28T17:31:30 [INFO]: LuaProc: 4|4|0 - 15 [48] - 4/4 - 120 bpm
2023-06-28T17:31:30 [INFO]: LuaProc: 4|4|640 - 15.3333 [109] - 4/4 - 120 bpm
2023-06-28T17:31:31 [INFO]: LuaProc: 4|4|1280 - 15.6667 [179] - 4/4 - 120 bpm
2023-06-28T17:31:31 [INFO]: LuaProc: 5|1|0 - 16 [243] - 4/4 - 120 bpm
2023-06-28T17:31:31 [INFO]: LuaProc: note off 60 64

The note-on in question (note 60) is in the very first line (right before transport wraps around to the start of the loop range), then I’m changing chords, notice that something’s not right, press stop, and presto, there the missing note-off for note 60 arrives in the last line.

Now I readily admit that I’m a lousy piano player, but there’s no way I could have accidentally kept that note pressed while changing chords, and I’m 100% sure that I didn’t press it at the time when I hit Ardour’s stop button. Even I would have noticed that. :wink:

Done since Ardour 7.5-78-gee0693d121

1 Like

Awesome, thanks Robin!

The plugin host does not sort events, they are passed though as-is.

The backend sorts events at port level - Ardour internal backends do that unconditionally. I do not recall what jack does. jack2 may only sort events when more than 2 ports are merged.

I guess we could special-case this for Lua scripts. Sorting events in C++ is much easier.

Edit: you can try to apply this patch that sorts events when compiling Ardour.

There seems to be an interaction with automation of arpeggiator parameters on the track here. If I turn that off, the hanging notes seem to go away. So it might be a bug in the arpeggiator after all. But then why would that cause the note-offs to be missed and delivered when I stop playback? Those note-offs get delivered through the midiin table, so they come from Ardour. I really don’t see how a bug in the arpeggiator might be causing this strange behavior.

This bug is getting weirder…

No big deal in Lua, table.sort() does it. :slight_smile:

JACK requires that events be delivered to a port (buffer) in time order.

Ok, I might give that a go then. But for the time being, I just sort midiout by timestamps on the Lua side, and that seems to work alright. Turns out that the hypothetical situation that I was worried about actually almost never arises in practice anyway. :wink:

Well, I was hoping that this might in some way be related to the weird delayed note-offs that I’m seeing in the midiin data, but no dice.

That bug still makes me scratch my head. I know that I’m getting some bogus MIDI data in midiin there, because I’m seeing all the right data at the right time in kmidimon if I monitor the track that sends the MIDI data to the arpeggiator track.

The question is why. Could it be that just copying Lua MIDI events from midiin to midiout (like midiout[i] = midiin[j]) wreaks havoc on the MIDI data of the Lua processor in some way? But that alone can’t be the issue, because that bug only occurs if at the same time I’m also playing automation on the arpeggiator track.

I’m stumped. Robin, if you can imagine a condition under which the midiin data passed to dsp_run could conceivably be messed up by a stupid bug in a plugin, I’m all ears. :slight_smile:

Wait, that luaState* object is local to each Lua plugin instance, right? Because if it isn’t, and midiin is a global variable in that Lua interpreter, then there be dragons.

Yes it is. Each Plugin instance has its own interpreter

A shot in the dark: Each Lua DSP processor is limited to 3MB (using a realtime safe memory pool – libs/ardour/luaproc.cc line 50).

Filling up 3MB with Lua Tables and local variable is quite a feat, but if your plugin caches a lot of Barlow tables perhaps there are some OOM issues?

Yeah, that came to my mind, too, because the only way turning on the automation affects the plugin is that some extra recomputations of the patterns are done, thus potentially using extra memory.

But that doesn’t seem to be it. This session is just 4/4, thus a single Barlow table with around 112 total entries of integer values. It’s not big by any means. To make sure, I actually measured the memory use with collectgarbage("count") at the end of dsp_run, and it’s consistently in the 1650 to 1680 KB range, way below the 3 MB limit.

I also tested my other theory about simply copying events from midiin to midiout, and made sure that I take deep copies instead, but that didn’t get rid of the bug either.

I think that I’ll just leave it at that for now. It only affects live usage of the arpeggiator in specific circumstances. I’ll surely stumble on the root cause of the issue at some point. For the time being, I can make the arpeggiator react to controller 123 on the input channel by flushing the chord and latched notes cache, so that you can at least get rid of the hanging notes by hitting the panic button.

1 Like

Done since Ardour 7.5-78-gee0693d121

Added some factory presets to both arpeggiators now (mostly various kinds of shuffles collected from my test sessions, more to be added later).

Works like a charm, thanks again!

1 Like