Do closures work at all in Javascript on Max?

Hans's icon

Hi,

I have just started to use Max (5.1.1, for Live) and I notice some irregularities when trying to establish callback functions written in Javascript. It seems that when passing functions to external objects (like LiveAPI), the function is passed by name (or, if I use a literal function, as a string) to the underlying object, invalidating all references to closed-over variables. Now, I can imagine how to work around this restriction by passing the callback arguments through 'this', but that is much less modular and much more error prone than just using closures. Is there any fix for this in the works?

Consider
function record_clip(track)
{
var clip_nr = find_free_clip(track);

function set_clip_length()
{
var live = new LiveAPI(this.patcher);
live.path = ['live_set', 'tracks', track, 'clip_slots', clip_nr, 'clip'];
live.set('loop_end', 4.0);
post('clip length set'); post();
}

var live = new LiveAPI(this.patcher, set_clip_length);

live.path = ['live_set', 'tracks', track];
live.set('arm', 1);

live.path = ['live_set', 'tracks', track, 'clip_slots', clip_nr];
live.property = 'has_clip';
live.call('fire');
}

which generates these errors:

js: livetest.js: Javascript ReferenceError: set_clip_length is not defined, line 148
js: livetest.js: Javascript ReferenceError: track is not defined, line 140

Thanks,
Hans

Jeremy's icon

I fixed some closure issues in jsliveapi post-5.1.1. Send me a PM with your email address and OS if you want to do some testing.

Jeremy

Jeremy's icon

Sorry, just realized that this forum software doesn't support PMs. Send me an email at jeremy@cycling74.com with that info, if you're interested.

Tom Swirly's icon

As I previously documented on this forum, closures don't work in callbacks only.

There's a simple workaround though!

// Tasker is a  class with a single method, Run, that applies the given function
// with the given "this" and args.
//
// Tasker works around the issue documented in
// http://tinyurl.com/max-js-task-destroys-closures.
function Tasker(object, method, args) {
  this.object = object;
  this.method = method;
  this.args = args;
  this.running = false;  // Perfect for adding a listener to!                                                  

  this.Run = function() {
    this.running || this.Execute();
  };

  this.Stop = function() {
    if (this.running) {
      this.task = this.task && this.task.cancel() && false;
      this.running = false;
    }
  };

  this.Execute = function() {
    this.Stop();
    this.method.apply(this.object, this.args);
  };

  // Schedule this.Loop to run after a delay.
  this.Schedule = function(delay) {
    this.task = this.NewTask();
    this.task.schedule(delay);
    this.running = true;
    return this.task;
  };

  // This method is overriden in tests.
  this.NewTask = function() {
    return new Task(this.Execute, this);
  };
};
Tom Swirly's icon
Macciza's icon

Hi
Maybe check out the "net.loadbang" stuff - I think there is a Groovy instantiation that may help you out....
Cheers