Interesting use-cases of javascript?
I've seen a lot of content on how to use Javascript in m4l / max msp.
However, I haven't yet found a clear idea on when to do the visual 'wire' programming or JS scripting, or when to do them together. When to use what? I'd love to hear your use-cases.
I'm quite experienced with programming, and find the wire interface quite chaotic.
Is this enough reason to do everything in javascript, and not learn the wire programming part? Or are there some drawbacks to that?
Are other people using javascript more in combination with wiring? e.g. only using scripting for the things that are not possible with the other? Thanks a bunch in advance !
The main issue (to me!) is that the JS issue only runs in the low priority thread, rendering it useless for anything timing related. It works great for jitter related work as that is all in the low-priority thread anyway, but any kind of midi output or sequencing is unreliable. It's also fine for manipulating data that you then work with in Max, ie using JS to fill buffers or tables, and then driving those separately from Max patches with a metro or midi objects. Node for Max also suffers here in that it's in a separate process, so you get some amount of somewhat unpredictable latency. This is one of the main reasons I created the Scheme For Max: so that one can do timing critical work in the high priority thread in a text language (S7 Scheme Lisp). It's still beta software but I am using it in real world projects for timing related tasks and it's working well. I will have a new release out in the next month or so, but you can check out the previous one's demo video, linked from the docs.
https://github.com/iainctduncan/scheme-for-max
https://www.youtube.com/watch?v=ErirIFCTdjg&t=4s
One pattern that I have been doing recently and quite happy with is using Scheme For Max as the main engine and using Node For Max as a way to do asynchronous jobs. I have S4M send messages to node, and get the response when it's done. This has worked really well for working with the underlying OS (i.e. file system directory scans and reading and writing) as Node has great libraries for all that kind of stuff, and hooking up a callback in Scheme is very simple.
Nice work. I'm curious about the high-priority features: do you have multiple threads? Do you handle both priorities for incoming messages with some machinery to stop them interfering with each other?
As an aside: I've done the embedded-languages-for-Max dance a few times. Back when Java was more mainstream, I implemented Groovy - with a REPL - then Python, then Clojure. (I have no idea if anyone still uses Groovy.)
https://github.com/cassiel/net.loadbang.groovy
https://github.com/cassiel/net.loadbang.jython
https://github.com/cassiel/net.loadbang.clojure
These were all notionally multi-threaded, with high- and low-priority support, although the concurrency was handled most convincingly in Clojure, as you'd expect.
At some later point I was batch cross-compiling ClojureScript into JavaScript, with a bit of Babel transpiling to get it to run inside JS and JSUI:
No REPL here, since the underlying JS/JSUI doesn't support it. And of course, all low-priority.
Later still, ClojureScript for Node for Max:
This is pretty recent and should still work well with minimal effort. REPL and network REPL (e.g. from VSCode or Emacs) work well, though you need an external server to do the file watching and reloading using something like Figwheel Main. (This is part of the build chain, most commonly used for hot-loading JS code in web pages.)
Most recently, SCI (the Small Clojure Interpreter), loaded into Node as a package with much less machinery:
A small, simplified Clojure, intended mainly for one-liners. I don't know offhand whether it might support a network REPL - that would be nice - but Babashka (built with SCI) does - though that might be something brought in by its own specific use of GraalVM and a bunch of Java libraries.
I'm sure there will be more of these kinds of embedded language in the future...
Hi Nick, thanks! I did check out your ClojureScript work when I was in the prelim research stage of this, and got it working. Now having used this in a real world context (www.weeksfeellikedays.com) I've found that being able to use the Node object in addition to the S7 interpreter is really handy, so I expect to dig back into it again later.
The work I'm doing on the next release is all focused on scheduling and threads for building sequencers and so on, as well as direct i/o to tables, buffers, and dicts. So the plan is to allow the user to specify which thread an s4m object runs in with an attribute for High, Low, or Any. If they choose High or Low, any incoming messages are promoted or deferred *before* hitting the S7 interpreter (while still in the C), thus protecting against having multiple threads calls into the S7 interpreter and clobber each other. I intend to add wrappers for critical_enter/exit so that advanced users can use Any if they want, but "officially" say: if you want both high and low threads, just make two s4m instances and treat it like an actor model, sending messages between them or using tables/buffers/dicts as ways to pass thread protected data. So far this seems to be working well, so I imagine my workflow will be to have a two s4m instances and a node instance with async communication between them.
(also I know of at least two companies using Groovy from my regular work!)
Yeah, running the whole instance at one priority level sounds like a good design choice. (If that choice is an attribute, though, what if someone tries to change it dynamically?)
It would be good to see your Scheme package in the official Max package manager...
Ah good point. I think the sensible thing is to do reset in that case, where reset wipes the interpreter, which is already what happens if you change the object box. And that can be thread protected. I'll have to make clear in docs that this is what will happen, but that makes sense to me. Thanks for the tip, I hadn't thought about that case yet.
And yes, when I get it to the point that I feel this is stable for wide use, I will certainly be asking to get it in the package manager. Hopefully this year, but want to make darn sure I've not got some thread issues in there. :-)