[ANN] lua~

Graham Wakefield's icon

Hi all,

The lua~ external is back online:

Lua~ is a Max/MSP external embedding the Lua language along with some
DSP functions and a scheduler supporting sample-accurate function
calling using coroutines. A lua~ object embedded in a Max patch can
load and interpret Lua scripts that receive, transform and produce
MSP signals and Max messages. Lua~ is particularly well suited to
granular synthesis, algorithmic microsound and accurate timing needs,
helpfully circumventing some of the limitations of the Max/MSP
environment for such work by supporting highly dynamic signal
processing graphs in parallel processes according to timing
specifications below block rate. Using an interpreted scripting
language within a graphical programming environment such as Max
offers advantages of control flow, generality of data and structure,
high precision and control, complexity of data and functional
interdependency and variable scoping.

Here's a very simple code example:

-- define a function
function beep(dur)
    -- play a decaying sine to the signal outlet for dur seconds:
    play(Out, dur, Sine(440) * Decay(dur))
end

-- repeat forever:
while true do
    -- launch parallel coroutine, function beep with duration 0-200 ms
    go(beep, math.random() * 0.2)

    -- wait for up to two seconds    
    wait(math.random() * 2)        
end

There are a few special advantages to using Lua - its memory
allocator/garbage collector and general efficiency supports
interpreting script code in the audio thread, which means that sample-
accurate function calls and control logic are possible. Lua also
supports coroutines (collaborative multithreading), kind of like
deterministic threads that are very cheap to run. I've extended them
to be aware of sample time, so they can be used like Routines or
Tasks in SuperCollider 3, but they can embed both control logic AND
synthesis graphs like SuperCollider 2, and also like shreds in ChucK.

The nicest aspect: you can generate, execute, change and remove any
number of any types of unit generators to the signal processing with
sample accuracy, below the block-size, at run-time, within
deterministic parallel execution flows.

The not so nice aspect: Binding Lua to Max/MSP is much less simple
than binding to Jitter, because of the underlying object model in the
SDK (Jitter objects have excellent reflection, while only the most
recently obexed' Max objects do). The main issue is that it is not
workable to create, connect and modify MSP objects from within a
script binding. Unfortunately this means that I had to write a new
set of elementary unit generators rather than re-using MSP object
code. I'm also in the process of binding the STK library, so a
standard, familiar set of ugens and so on will be available.

Currently OSX only, but maybe I can get a windows box to compile the
XP version in the next few weeks.

Graham

www.grahamwakefield.net

Anthony Palomba's icon

Sounds awesome Graham, I am really looking forward
to giving the XP version a spin. Please keep us
posted.

Anthony

Rob Ramirez's icon

yeah, it sounds really great.
will try it out as soon as i get some free time.

it seems you are implying dynamic modifications to the signal chain without the familiar msp pop, is this correct?

Graham Wakefield's icon

This is correct - and also independent of block-rate*. For example:

-- changing an FM modulator graph at increasing periods from 1 to
10000 samples:

-- duration of one sample in seconds:
local sample = 1/samplerate

-- add a sinewave to the output bus:
local carrier = Out:add(Sine())

for i=1, 100 do
    -- set pulse duration:
    local dur = i * i * sample
    print("duration", dur, "(s)")

    -- create new modulator
    carrier:freq(Sine(10, 0) * 500 + 1000)
    wait(dur)

    -- create new modulator
    carrier:freq(Square(20, 0) * 250 + 1500)
    wait(dur)
end

Out:remove(carrier)

Graham

*Though I have to stress the point - because of the current MSP
architecture, I cannot use existing MSP unit generators inside of
lua~, so I had to provide new unit generator DSP functions.

On Oct 26, 2007, at 11:08 AM, Robert Ramirez wrote:

>
> yeah, it sounds really great.
> will try it out as soon as i get some free time.
>
> it seems you are implying dynamic modifications to the signal chain
> without the familiar msp pop, is this correct?

grrr waaa
www.grahamwakefield.net

Gary Lee Nelson's icon

I download lua~ and tried it. It works very nicely but seems to cost a lot
in cpu for even simple patches.

On 10/26/07 2:52 PM, "Graham Wakefield" wrote:

> This is correct - and also independent of block-rate*. For example:
>
> -- changing an FM modulator graph at increasing periods from 1 to
> 10000 samples:
>
> -- duration of one sample in seconds:
> local sample = 1/samplerate
>
> -- add a sinewave to the output bus:
> local carrier = Out:add(Sine())
>
> for i=1, 100 do
> -- set pulse duration:
> local dur = i * i * sample
> print("duration", dur, "(s)")
>
> -- create new modulator
> carrier:freq(Sine(10, 0) * 500 + 1000)
> wait(dur)
>
> -- create new modulator
> carrier:freq(Square(20, 0) * 250 + 1500)
> wait(dur)
> end
>
> Out:remove(carrier)
>
>
> Graham
>
> *Though I have to stress the point - because of the current MSP
> architecture, I cannot use existing MSP unit generators inside of
> lua~, so I had to provide new unit generator DSP functions.
>
>
>
> On Oct 26, 2007, at 11:08 AM, Robert Ramirez wrote:
>
>>
>> yeah, it sounds really great.
>> will try it out as soon as i get some free time.
>>
>> it seems you are implying dynamic modifications to the signal chain
>> without the familiar msp pop, is this correct?
>
> grrr waaa
> www.grahamwakefield.net
>
>
>
>

Cheers
Gary Lee Nelson
Oberlin College
www.timara.oberlin.edu/GaryLeeNelson

Graham Wakefield's icon

Hi,

I had to rewrite lua~ almost from the ground up to get around many
issues that were causing crashes due to multithreading (and it not
always being exactly clear what calls are or are not thread-safe in
the SDK). In the process, a lot of the optimizations I had
previously had been removed, in order to reduce the number of
possible causes. This week I'm going to re-introduce them one by
one, and providing they all work, you should see quite a considerable
improvement in CPU efficiency.

((That said, interpreted control, run-time modification and even
generation of DSP graphs, with sample-accuracy, is never going to be
as efficient as a static graph of C externs. But it can get close
enough to be very useful...))

Once the efficiency changes have been implemented, I'll post again to
let you all know.

Best,

Graham

On Oct 27, 2007, at 5:56 AM, Gary Lee Nelson wrote:

> I download lua~ and tried it. It works very nicely but seems to
> cost a lot
> in cpu for even simple patches.
>
>
> On 10/26/07 2:52 PM, "Graham Wakefield"
> wrote:
>
>> This is correct - and also independent of block-rate*. For example:
>>
>> -- changing an FM modulator graph at increasing periods from 1 to
>> 10000 samples:
>>
>> -- duration of one sample in seconds:
>> local sample = 1/samplerate
>>
>> -- add a sinewave to the output bus:
>> local carrier = Out:add(Sine())
>>
>> for i=1, 100 do
>> -- set pulse duration:
>> local dur = i * i * sample
>> print("duration", dur, "(s)")
>>
>> -- create new modulator
>> carrier:freq(Sine(10, 0) * 500 + 1000)
>> wait(dur)
>>
>> -- create new modulator
>> carrier:freq(Square(20, 0) * 250 + 1500)
>> wait(dur)
>> end
>>
>> Out:remove(carrier)
>>
>>
>> Graham
>>
>> *Though I have to stress the point - because of the current MSP
>> architecture, I cannot use existing MSP unit generators inside of
>> lua~, so I had to provide new unit generator DSP functions.
>>
>>
>>
>> On Oct 26, 2007, at 11:08 AM, Robert Ramirez wrote:
>>
>>>
>>> yeah, it sounds really great.
>>> will try it out as soon as i get some free time.
>>>
>>> it seems you are implying dynamic modifications to the signal chain
>>> without the familiar msp pop, is this correct?
>>
>> grrr waaa
>> www.grahamwakefield.net
>>
>>
>>
>>
>
>
> Cheers
> Gary Lee Nelson
> Oberlin College
> www.timara.oberlin.edu/GaryLeeNelson
>
>

grrr waaa
www.grahamwakefield.net

Graham Wakefield's icon

Hi all,

Just updated the lua~ external (still only OSX, sorry). A few minor
fixes, and some optimizations.

Best,

Graham

On Oct 27, 2007, at 3:23 PM, Graham Wakefield wrote:

>>
>>
>> On 10/26/07 2:52 PM, "Graham Wakefield"
>> wrote:
>>
>>> This is correct - and also independent of block-rate*. For example:
>>>
>>> -- changing an FM modulator graph at increasing periods from 1 to
>>> 10000 samples:
>>>
>>> -- duration of one sample in seconds:
>>> local sample = 1/samplerate
>>>
>>> -- add a sinewave to the output bus:
>>> local carrier = Out:add(Sine())
>>>
>>> for i=1, 100 do
>>> -- set pulse duration:
>>> local dur = i * i * sample
>>> print("duration", dur, "(s)")
>>>
>>> -- create new modulator
>>> carrier:freq(Sine(10, 0) * 500 + 1000)
>>> wait(dur)
>>>
>>> -- create new modulator
>>> carrier:freq(Square(20, 0) * 250 + 1500)
>>> wait(dur)
>>> end
>>>
>>> Out:remove(carrier)
>>>
>>>
>>> Graham
>>>
>>> *Though I have to stress the point - because of the current MSP
>>> architecture, I cannot use existing MSP unit generators inside of
>>> lua~, so I had to provide new unit generator DSP functions.
>>>
>>>
>>>
>>> On Oct 26, 2007, at 11:08 AM, Robert Ramirez wrote:
>>>
>>>>
>>>> yeah, it sounds really great.
>>>> will try it out as soon as i get some free time.
>>>>
>>>> it seems you are implying dynamic modifications to the signal chain
>>>> without the familiar msp pop, is this correct?
>>>
>>> grrr waaa
>>> www.grahamwakefield.net
>>>
>>>
>>>
>>>
>>
>>
>> Cheers
>> Gary Lee Nelson
>> Oberlin College
>> www.timara.oberlin.edu/GaryLeeNelson
>>
>>
>
> grrr waaa
> www.grahamwakefield.net
>
>
>
>

grrr waaa
www.grahamwakefield.net

namaste ranch's icon

Thanx for all you've done with lua~, Graham!

I've downloaded beta 6 and have been playing with it. I get the impression that lua~ is mostly designed for synthesis. So far, however, I've had no luck using it to replace some of the more logic and control functions I'm currently using Javascript for on a project... basically scripting the Jitter (and eventually MSP) processing chains. So some questions, please:

Is it possible to pass variables in and out of lua~? The right output only seems to want to print to the Max window, and the other outs only seem to want the numeric output of ugens. If I could figure how to use Math or other ugen for the control calculations for the variables, I'd still need an I/O method for them (and I have a ton of arrayed vars). Looks like bus and play pretty much just push out numbers with no provision for identifiers. Assuming Math or other method can handle calculations, I thought maybe a worst-case I/O scenario would be to dedicate discrete ins and outs to handling communication of discrete variables... but I can't figure out how to get specific data in/out a specific bus. And if any/all of the above is possible, is it possible with local as well as global variables? If possible, examples of these issues would really help!

Also, is it possible to have global variables shared across multiple instances of lua~ (and Lua scripts)?

Is it possible to embed an actual lua~ script inside a Max runtime for the purpose of hiding it from the public?

Thanks so much!

Jeff

Gregory Taylor's icon

> Is it possible to embed an actual lua~ script inside a Max runtime for the purpose of hiding it from the public?

Heaven forbid that the public should actually know
this stuff. Of course, if you're hiding it in a situation
where you're charging "the public" money, did you check
on clearance to use this in something that lines your
pockets? :-)

namaste ranch's icon

Hi, Gregory...

I VERY MUCH appreciate the open, sharing nature of Max and the Max community.

Actually I was thinking more of an installation situation where one does everything they can to secure it from hardware theft and software tampering. If somebody with ill intent should gain access to the system, messing with an exposed script could compromise the entire public experience. Hence my question about "hiding a Lua script from the public". (I'm pretty sure the answer is negative by the very nature of scripts, but thought somebody might prove my perception wrong.)

Anybody have any answers to my other lua~ questions?

Thanks!

Jeff

Anthony Palomba's icon

Is there any word as to when there will be a windows version of lua~?

Graham Wakefield's icon

Whoops, sorry for the delay, I've been taking (hopefully) the last
exam in my life and it is a big one. Still not finished actually.

On Aug 31, 2008, at 1:55 PM, Jeff Burger wrote:

>
> Thanx for all you've done with lua~, Graham!
>
> I've downloaded beta 6 and have been playing with it. I get the
> impression that lua~ is mostly designed for synthesis.

Well, I would say computational approaches to the control of
synthesis, esp. microsound, but yes. That's what I was into when I
made it.

> So far, however, I've had no luck using it to replace some of the
> more logic and control functions I'm currently using Javascript for
> on a project... basically scripting the Jitter (and eventually MSP)
> processing chains.

I've had more joy with jit.gl.lua for scripting Jitter, but of course
I have a little language bias. Unfortunately it's not as
straightforward to script the MSP chains, as the way the chains are
built isn't really suited to rapid construction/destruction/
reconfigurations, esp. at microtime. That's what lua~ was built for.
You could think of it as a highly stripped down SC2 in a max object.

> So some questions, please:
>
> Is it possible to pass variables in and out of lua~?

Of course.

send variables in:
call, dostring, and set methods:
http://www.mat.ucsb.edu/%7Ewakefield/lua%7E/lua%7E.htm#call

Anything sent out will go to the right-most outlet. In the help
patch, this is routed to the Max window by default, but you can do
whatever you like with it.

> If I could figure how to use Math or other ugen for the control
> calculations for the variables, I'd still need an I/O method for
> them (and I have a ton of arrayed vars). Looks like bus and play
> pretty much just push out numbers with no provision for
> identifiers. Assuming Math or other method can handle calculations,
> I thought maybe a worst-case I/O scenario would be to dedicate
> discrete ins and outs to handling communication of discrete
> variables... but I can't figure out how to get specific data in/out
> a specific bus. And if any/all of the above is possible, is it
> possible with local as well as global variables? If possible,
> examples of these issues would really help!

It's a little hard for me to understand what you're asking. So, lua~
deals with two kinds of data, just like MSP. Audio signals are sent
in to the inlets of the object and are represented in the lua script
as the In bus. You can access individual channels of the bus as In
[1], In[2] etc., representing the individual inlets of the lua~
object. The outlets are represented as the Out object. Right now
there's no direct way to write to individual channels of Out, except
using the Pan ugen, sorry. Anyway, these end up coming out of the
lua~ object outlets. Connecting up a graph between In and Out is
what the lua script can do, and it can change it according to
functions, while loops, coroutines and so on, scheduled in time using
go() and wait(). The second kind of data are messages in Max, and so
messages sent to lua~ are converted into equivalent Lua data types.
The Max messages call, dostring and set will all give you ways to
send message data into lua~ and call functions in the script.
Finally, ugens in lua~ can mostly take either other ugens or numbers
for their parameters.

I'm not sure what variables you mean when you say you need an I/O
method for them. Do you want to send max lists and process them as
audio? If so, this isn't really possible as such, lua~ is intended
for real-time processing of audio streams.

Bus and play() are just ways to connect unit generator graphs with
active streams, dynamically. There are no identifiers, because these
are just audio streams. The identifier as-such is the Lua variable in
the script.

I'm not sure what you mean by global/local variables, but all
variables in a script can be global/local, it makes no difference to
Max.

OK an example attached at end of this mail. HTH

> Also, is it possible to have global variables shared across
> multiple instances of lua~ (and Lua scripts)?

Not in the current version, but it *could* be possible.

> Is it possible to embed an actual lua~ script inside a Max runtime
> for the purpose of hiding it from the public?

If you mean a collective/standalone, then yes, just like any other
max object or file associated with a patch. The public will be able
to see it however if they Show Package Contents on the collective/
standalone. FYI.

BTW, lua~ is working more-or-less in Max5, except for the editor
window etc. This may be updated when the max5 SDK is released, and
probably the windows version around the same time. I have quite a
few improvements for a v2, but no time. Bloody exams.

Max Patch
Copy patch and select New From Clipboard in Max.

Save the following as 'love.lua' in the same folder:

-- make a little graph:
local sine = Sine()
local graph = Wrap(0.2, 0.8, sine)

-- connect to signal outlet
Out:add(graph)

-- a global function is accessible to Max via the 'call' message:
function choosesource(which)
    if type(which) == "number" then
        -- #In gives the number of channels In has. Works on all ugens.
        if which > 0 and which
            -- get a local reference to the inlet's audio stream:
            local inlet = In[which]
            -- set this as the frequency of the graph:
            sine:freq(inlet)

            outlet("changed the graph to inlet", which)
        else
            outlet("attempt to choose a nonexistent inlet:", which)
        end
    else
        outlet("choose function expects a number to select the inlet")
    end
end

print('I love exams')

Finally, I probably won't upgra

//~'s icon

it's not coming back, is it?

Iain Duncan's icon

I have not heard of it coming back, and I believe (conjecture here....) that Graham's work is focused now on Gen. I think the threading situation became more complex; it was post Max 5 than the JS object also got locked to the low priority thread if I recall correctly. I'd love to hear the correct history/reasons from someone on the inside though if they can share, I'm sure there are good reasons for how it works.

If you are wanting dynamic scripting in the high priority thread, you can do this with Scheme for Max, but it does not (yet) do MSP stuff. (And may never, I don't know yet what I will hit with threading issues). It is unfortunately not simple to share an interpreter across threads. In Scheme for Max I've made it so that the interpreter is always in the scheduler/high thread or gui/low thread (you choose on instantiation). I do plan in future to try another version where the interpreter lives in the audio thread, but no idea how well that will go!

Scheme for Max lives here if you're interested: https://github.com/iainctduncan/Scheme-for-max

It uses s7 Scheme, which is linguistically quite different from Lua, but does share many of the same pros and cons. They both have been used for example as embedded interpreters in video game work.

There does exist a lua object for Pd, and in Pd there is only one thread, so if the Lua part is more important than the Max part, that might be an option. (I've also released Scheme for Pd recently).

Graham Wakefield's icon

Wow, revived from 13 years ago!

Yes my Max focus (and available time) shifted to gen~ and other things (and then life as a prof didn't allow as much time as life as a grad student did), though I still have great fondness for Lua. I remember I had some newer code built using the amazing LuaJIT engine that was fast enough to do some pretty intensive DSP, but unfortunately I can't realistically see wrapping this back into a Max object up making it onto my work schedule any time soon.

Scheme is a beautiful language.

Iain Duncan's icon

And of course there is also Graham's involvement in Daisy, another great project, from which I just got my pod! Thanks for everything you've done Graham, I'm looking forward to Scheme-controlled Daisy synths. :-)

//~'s icon

I love s4m. Had a big first project going strong. The problem is that my previous laptop broke down, I now have an M1 Macbook, and it's unsupported. So I reckoned I'd learn Lua for my scripting, as it's Max native, feels more future-proof. And I've always wanted to have unit generators in Max, so lua~ sounds like a fit.

I miss the fluidity of Scheme so much.

Iain Duncan's icon

Hi, I'll get s4m compiled for M1 soon, it's just I don't have access to an M1 machine and so far have not been able to find anyone with one with the chops to help me get it compiled, so there is a bunch of stuff to do. I should be able to get it done in the next couple of months though. I'll be able to dedicate some time to this in February. I tried asking for M1 compilation help from the community, but no luck so far unfortunately, perhaps I'll try again!

Regarding Lua, what do mean by Max native though? AFAIK There is no native Lua support in Max, the lua object is 3rd party external, just like s4m. The only native options AFAIK are JS, Java, and Gen. For what it's worth, s4m is definitely continuing, it's my master's thesis topic right now and I will be doing a PhD after that continuing, so it's not going to stop being developed!

Iain Duncan's icon

I will also be getting it into the package manager, but the policy is (understandably) that packages need to support M1, so next year!

Shakeeb Alireza's icon

Hi,

I have just created a very basic luajit~ external which is available in https://github.com/shakfu/luajit_max

Is it possible to access the old lua~ external code to maybe revive parts of it in this luajit external?

Also does anyone know how to activate lua code highlighting in the builtin max code editor?

Thanks!

S

Iain Duncan's icon

Just commenting because this thread got revived. Scheme for Max 0.4 does support Apple Silicon/M1/M2

congrats on the Lua work, btw!

Graham Wakefield's icon

I had a hunt around to see if I could find the original lua~ code, but I can't find it locally and it was pre-github days. I probably have it on a backup disk somewhere, but I'm away from home and won't be able to access that any time soon unfortunately.

As mentioned though, I did spend quite a bit of time (about 10 years ago) with a variety of luajit-based audio stuff as part of the LuaAV project, with code floating about in a few different repos that were all unfortunately abandoned because I just didn't have time. I even had a luajit~ external mocked up in 2012, but never released -- and I can't find the source code for that either.

I did find some fragments of code here, if this can help, including:
- The lua code for the lua~ scheduler (the bit that did the go(), wait() etc. calls). This is a pretty cool scheduler (ab)using coroutines for temporal scheduling, capable of things like temporal recursions, nested temporal loops, etc.

- An example lua file demonstrating the FFI interface for a couple of Max internals, e.g. gensym, atom_get / atom_set, print() etc., which might be a good starting point for hooking LuaJIT to Max without needing extra overhead,

- A generic templated header-only C++ file to make it much easier to bind C/C++ to a lua/luajit state, etc. that we found incredibly useful for a lot of projects.

LuaJIT is insanely fast, definitely fast enough to do some audio DSP, and honestly I'm amazed nobody else has picked up on this possibility in the decade since. It just needs a few essential Max/MSP things exposed through an FFI interface, and most of the rest could be scripted. I'd be happy to advise or contribute to the extent that time allows.

One of the tough questions is how to deal with threading. LuaJIT really wants to be single-threaded, but should that be running in the MSP audio thread, or one of the Max message threads?
- The original lua~ put it in the audio thread so you could do crazy sample-accurate DSP graph changes, but that means that all input messages to the lua~ object have to be ferried over to the MSP thread via some kind of message queue, and vice versa for messages coming out (including debug prints etc.). Probably a pair of single-reader single-writer FIFOs can cover these needs. The other risks are that infinite loops will crash the audio, unpredictable memory allocations and garbage collections might cause audio dropouts (though there are things you can do to minimize those), etc.
- Alternatively, if you put the Lua code in the Max messaging thread, you can use FFI etc. to talk directly to Max, and have no memory headaches, but then you have to abstract the audio DSP somehow... and that almost surely still needs some kind of messaging system to change audio graph states, particularly dealing carefully with how you delete things. This is what we did for the LuaAV project, and it worked well but the abstraction meant that it wasn't as expressively open-ended for audio DSP as lua~.

Shakeeb Alireza's icon

Graham,

Thanks very much for responding in such detail. Since the external just started a few day ago, I really have not considered many of the problem areas that you have covered (threading, messaging, etc..) so all your comments here are extremely helpful. Of course if there is any code that one can learn from, that would also be of tremendous help.

I wanted to create a luajit external to test how luajit would perform in Max's audio thread, and also create an excuse to learn the lua c-api and luajit's famous ffi-interface.

So far, my current implementation is really just a proof-of-concept and has a single lua function running in the audio thread to enable making changes to the audio stream via editing this function and saving the underlying lua script. Although this works so far in the most basic sense, the performance is impressive, and I am now interested to see how I can push this further and implement some basic dsp structures ... for example, a simple low pass filter which requires 'sample memory', etc..

S

Iain Duncan's icon

Hi Shakeeb, while I don't have time to take on another project in earnest, if anything I did in Scheme for Max is useful, please feel free to hit me up with questions. I would think some of the way I handle incoming and outgoing messages might be helpful. I would be happy to help in whatever capacity I can!

Shakeeb Alireza's icon

Thanks Iain. That's much appreciated. A lot of this is new to me, so I'll probably take a lot of time experimenting and learning before I try to make this generally useful. (-:

Iain Duncan's icon

Your most welcome. While older, the Eric Lyon book on making Max externals is still very good IMHO.

iain

Shakeeb Alireza's icon

In case anyone is interested, I just committed a few basic examples of some single pole lpf filters and a saturator which seem work. These are implemented as lua functions and the underlying lua dsp function running in `perform64` can be changed via a drop down. So far luajit performance is impressive.

Shakeeb Alireza's icon

Fyi, there are now two externals in the aforementioned luajit_max project: luajit~ and luajit.stk~. The first is intended for pure lua dsp code running in the audio thread with examples from the worp project. For the second, I have wrapped the c++ synthesis toolkit classes and exposed them to luajit.

So there's quite a bit to work with in case anyone's interested.

Iain Duncan's icon

Wow, wrapping the stk sounds great! well done. :-)

Shakeeb Alireza's icon

Thanks Iain (-: