Lua arpeggiator plugin anyone?

You provide motivation to finally expose time information to Lua DSP scripts.

Nice! That’s exactly what I need, thanks a lot.

Plugins have a local latency-compensated time.

Ok, I see. That’s probably why my BBT changes detected with that method were always a bit off. :slight_smile:

It works, and by is shown in Window > Log.

Ah yes, that’s good to know.

Please let me know if that works for you and if you have further suggestions before we finalize this API with the 7.5 release.

Looks good to me. But would it be possible to have actual BBT and the length of the current cycle in ticks as well? Having that kind of information readily available would make things a little easier for my purposes, although that information can be computed relatively easily from the data that’s already there, I guess.

But would it be possible to have actual BBT and the length of the current cycle in ticks as well?

Scratch that. Ticks won’t actually be all that useful in this context. If you want to do sample-accurate playback then something like a sample count since the last quarter beat might be more useful. In any case, it should be possible to get all these numbers from what’s already there.

From an API POV the sample-position of the last beat would be more consistent. Then you can get sample count since the last quarter note by subtraction :
time["sampleTime"] - time["beatPosition"]

just git pushed as

1 Like

Agreed.

BTW, what’s up with all the foo["bar"] indexing in the sample Lua code, is that a stylistic thing, or personal preference? I’m sure you know that in a Lua table, you can always just write the equivalent foo.bar instead (of course, this only works if the symbol is a proper Lua identifier). I also find it more convenient and readable to write something like

midiout[cnt] = { time = time, data = data }

which is equivalent to

midiout[cnt] = {}
midiout[cnt]["time"] = time;
midiout[cnt]["data"] = data;

I’d even say that the former is more idiomatic Lua, but of course everyone has their own coding style. I’m just curious. :wink:

One more thing, though: The sampleTime from the time_info table is local latency-compensated time, right? So I can just pass that to Temporal.TempoMap:bbt_at() to get accurate and up-to-date BBT information?

The reason I still need the BBT info from the tempo map is that I need to be able to properly deal with more esoteric time signatures such as 11/8 or 13/16 that don’t evenly divide into quarters. Now one could derive that data from what time_info provides, but that would be cumbersome – Ardour already knows how to compute those values, so I’d rather use those. I can still put time_info to good use to detect meter changes and base pulses, so that I only need to invoke the BBT calculation when a new beat is due.

It’s based on code copied from C/C++. Also, when I add Lua bindings and write a quick example script I still have a C mindset.

Yes it is.

I think I also was too quick last night, exposing “sample-position of the last beat”. The value is not useful if there are tempo-ramps, or if the tempo-changed since the last beat.

I’m also not a big fan of using camelCase here (in C++ we only use it for classes and enums), so far I’ve just based it off VST 3 Interfaces: ProcessContext Struct Reference (and steinberg apparently likes camels).

I am thinking of changing it to underscores. e.g. “sample_time”, which is more consistent. Even more Lua-like would be nested tables: time.sample.start, time.sig.numerator etc. except there is the problem that end is reserved keyword we we cannot use time.sample.end, but time.sample_time_end work.

Any thoughts?

I think I also was too quick last night, exposing “sample-position of the last beat”. The value is not useful if there are tempo-ramps, or if the tempo-changed since the last beat.

I agree that it’s better to remove it again. It’s of limited use anyway, and this kind of information can be determined through the tempo map if needed.

And what about musicTimeEnd? That one is actually much more useful, since it lets you detect imminent beat increments, in order to schedule MIDI notes at the right sample time. For that to work, it should always equal the next cycle’s musicTime. Is that always true, regardless of any tempo changes?

I’m also not a big fan of using camelCase here

I’m firmly with you on that one. :slight_smile: Lowercase + underscores FTW!

1 Like

Yes, that should be the case.

Also note the end is inclusive. sampleTimeEnd = next cycle’s sampleTime, same for musicTime and musicTimeEnd.

It is reminiscent of the fence-post problem. e.g. play up to beat 2, and in the next cycle: start playing from beat 2.

Great. It should be an easy search/replace in your script. I’m sorry for the inconvenience.

I’m sorry for the inconvenience.

Better change it now than never.

Done.

I’m tempted to remove time["bar"], which is somewhat ill defined (and assumes 4/4). Are you using that?

I’m tempted to remove time["bar"], which is somewhat ill defined (and assumes 4/4). Are you using that?

Not really. I’m using beat and ts_denominator to detect beats, so that I don’t have to access the tempo map in each cycle, and then sample to get proper BBT information from the tempo map. I think that for sample-accurate triggering I’ll also need beat_end and sample_end, and maybe tempo and tempo_end, but that’s about it.

This is looking good. I have a basic arpeggiator working now, which already does the job fairly well. I still need to figure out sample-accurate triggering, though, where things are likely to get complicated. I’ll look into that tomorrow.

1 Like

Ok, there we go: simple_arp.lua - Google Drive

This does the job reasonably well for me, also works great for playing drums. :wink:

The sample-accurate triggering wasn’t trivial, but not rocket science either. So the time_info API, as it stands, seems to work reasonably well for this kind of thing.

Please let me know if you spot any bugs or other mishaps. I wasn’t sure how to categorize this kind of MIDI effect, so an effect it is for now, which also makes it easy to find.

I’ll eventually upload this example, along with some other Ardour Lua scripts I’ve done for the students, to GitHub. But for the time being feel free to add this to the Ardour source, as you see fit.

1 Like

It works nicely in a quick test. Amazing!

At first I was confused that nothing played.

Perhaps, when the transport is stopped MIDI messages can be passed though as-is?
make uncomment to pass through input notes transport dependent (Session:transport_rolling()).

Also transport start/stop should perhaps send all-note-off messages.

PS. It is super fun to play around with this!

1 Like

Perhaps, when the transport is stopped MIDI messages can be passed though as-is?
Also transport start/stop should perhaps send all-note-off messages.

Good ideas, will do.

Yeah, it’s a little thing, but I also found it to be stupidly fun, especially when I hook it up to the avldrums. :slight_smile: Hope the students will like it, too. (I’m doing a DAW course this semester, mostly about Ardour. It’s a lot of fun, especially after I rediscovered its Lua interface. You’ve done a great job with this, it’s come a long way since I last dabbled around with it.)

I’d still like to do an alternative version of the plugin with better auto-generated velocities, using Barlow indispensabilities. I already have that as Lua code, so it will be easy to port over.

1 Like

P.S.: Do you know of any other DAW which offers such a scripting interface? I mean real scripting, not just controller interface stuff.

Reaper has a fairly extensive scripting system, which includes being able to create GUIs (ours doesn’t allow this).

Hi Paul, you’re right, that’s ReaScript, I should have known this. Thanks for reminding me.

This is very cool. Just curious: Can you record the arpeggios your script creates in a seperate midi track so one can mess around with them later?

Yes, you can.

Create a MIDI track without synth plugin, add the ARP, then connect the output of that track to a new MIDI track to record on.

If you want to play live into the arp, you do not even need a separate track. You can change the Disk I/O to “Post Fader”, or like in this example to “Custom” and place the Arpeggiator at the top:
image

1 Like