Solutions for JS scripting in M4L?

Questions and discussion about building and using Max for Live devices
Post Reply
amounra93
Posts: 432
Joined: Sat Jan 24, 2009 8:16 pm
Location: Arcata, CA
Contact:

Solutions for JS scripting in M4L?

Post by amounra93 » Thu Dec 10, 2009 5:18 am

Ok.

I apologize in advance for stupid questions, I'm really new to JS (in fact REALLY new...I've skimmed over stuff in the past, but now I'm building working scripts, so bear with me).

I started a new thread in the hopes that more js specific info will find its way here. The beginning of this thread really began here:

http://forum.ableton.com/viewtopic.php?f=35&t=128452

I figured out that you can assign global variables inside of functions without using the "var" prefix when making assignments (even though this is not really working as expected, either).

I am attempting to assign liveAPI variables inside of a function, so that the function can be called AFTER the Live API has instantiated itself. Loadbang() doesn't work. (Thanks, btw, to jayemell for some insight into a good way to deal with this, which I'm working on right now).

My current objective is to build a simple js to deal with the Launchpad's User2 controls. I've already accomplished this with the standard Max4Live objects, but I just can't leave well enough alone, and there a couple of things I see as being more advantageous in using js. I was able to build a script that worked last night, but it won't instantiate at load as it should, so I'm having to rethink some things. Not to mention the code is very cumbersome, I know there have to be some better ways to do this.

I have several questions, though. Right now, I have a constructor for each control on the launchpad, coupled with a callback for the same. In addition, I've built functions for each control to forward color values to the hardware. Again, cumbersome. I feel like there should be a way to call the variables containing the api_paths from outside the js, but haven't so far figured out how.

It looks like this:

Code: Select all

autowatch = 1

setinletassist(0, "grid input");
setoutletassist(0, "grid output");


cs = "0"  // control_surface# to be replaced upon selection

function init()
{
    _00 = new LiveAPI(this.patcher, return_00, "control_surfaces", cs, "controls", 2);
    _00.property = "value";
}

function _00(val)
{
    _00.call("send_value", val);
}

function return_00(v)
{
    outlet(0, 0, 0, v.slice(-1));
}
Input is in the form of _00 1 (formatted from a "sprintf _%i%i %i" object prior to the inlet, so that the function may be called from a numerical value, since apparently functions can't be numerical....again, I'd rather call the function in a different way so I would need only one, but haven't discovered how yet).

Callbacks are working fine, but functions are not being called correctly, and I'm assuming this is because I'm assigning the control_surfaces / controls from inside a function.

I realize that it will require a static instance of each API path for every control I'm trying to access. But I'm trying to get around needing a seperate function for each one. Isn't there a way to reference the original API object after its constructed via an "init" from a variable inside another function? What I'd REALLY like to do is set up all the controls/callbacks and then use a function to route values to its appropriate destination, like:

Code: Select all

function sendvalue(control, value)
{
  control.call("send_value", value);
}  
Thus coded, though, obviously doesn't work.

Thanks for reading:) Collaboration/instruction/heavy reprimanding most welcome and eagerly anticipated. Like Eek the Cat says, "it never hurts to help!"


EDIT::

Is it possible to put LiveAPI contstructions inside an array? This would probably solve some of my problems....
http://www.aumhaa.com for Monomod and other m4l goodies.

amounra93
Posts: 432
Joined: Sat Jan 24, 2009 8:16 pm
Location: Arcata, CA
Contact:

Re: Solutions for JS scripting in M4L?

Post by amounra93 » Thu Dec 10, 2009 6:16 am

Code: Select all

button0_0 = new LiveAPI(this.patcher,  "control_surfaces", cs, "controls", 2)
button0_0 .property = "value"
post("button 0_0" + button0_0 .id)

button0_1 = new LiveAPI(this.patcher,  "control_surfaces", cs, "controls", 3)
button0_1 .property = "value"
post("button 0_1" + button0_1 .id)

var controls = new Array
controls[1]=button0_0
controls[2]=button0_1
post(controls[1].id, controls[2].id)

function print_button(b)
{
    post(controls[b].id);
}
So this works, but apparently I can only reassign a LiveAPI object to an array through use of its associated variable, and not directly. Or am I doing something wrong?

Code: Select all

var controls = new Array
controls[1]= new LiveAPI(this.patcher,  "control_surfaces", cs, "controls", 2)
....doesn't work. Makes no sense to me. But at least I'm headed in the right direction?


EDIT::

Code: Select all

cs = "0"  

var controls = new Array
controls[1]=new LiveAPI(this.patcher,  "control_surfaces", cs, "controls", 2)
controls[2]=new LiveAPI(this.patcher,  "control_surfaces", cs, "controls", 3)
post(controls[1].id, controls[2].id)

function list(b, v)
{
    controls[b].call("send_value", v);
}
Whatever....this works now. Go figure, wouldn't work earlier. Good to know arrays are functional with the API object, though. Maybe my soliloquoy will help someone else out :)
http://www.aumhaa.com for Monomod and other m4l goodies.

steff3
Posts: 330
Joined: Sat Jul 10, 2004 10:16 am

Re: Solutions for JS scripting in M4L?

Post by steff3 » Thu Dec 10, 2009 6:48 am

>>
since apparently functions can't be numerical....agai
>>

Well, function msg_int(), msg_float() --- I think you make your life harder than it should be. There is a simple JavaScript part in the Max docu - especially for such things. Well, candy-Max5 docu is suboptimal, so get a previous version where you get actual PDF files which is more useful IMHO.

you can also input lists and stuff. So you could also input paths ....

Well, I use some - what toy Javascript - calls global vars (global is pretty script local IMHO) -

like var api, var p(atcher) and vars for those things I want to reference in all functions.

then in a function you can call api.path("path") or api.goto("path"), right.
the path string I normally construct right before the call api.path(...),

are semicolons just used by C-family programmers in JavaScript or are they mandatory?

What about the brackets for e.g. new Array() (also just started to use JS, so I am not savvy )

hope that helps

best

amounra93
Posts: 432
Joined: Sat Jan 24, 2009 8:16 pm
Location: Arcata, CA
Contact:

Re: Solutions for JS scripting in M4L?

Post by amounra93 » Thu Dec 10, 2009 7:44 am

Well, function msg_int(), msg_float() --- I think you make your life harder than it should be.
I figured out msg_int (and am actually now using "list" successfully) right after I posted that last bit. I'm definitely making life harder than it need be LOL. But that's part of the learning process, I guess.
then in a function you can call api.path("path") or api.goto("path"), right.
the path string I normally construct right before the call api.path(...),
I tried this approach initially with the Max objects, but calls to live_path are too slow. I assumed (and believe I read elsewhere) that the same lag would result when utilizing the js LiveAPI object. That's why I was building static objects for each control (not to mention, there must be a static path for each observer set by the .property method if you wish to receive callbacks from more than one property at a time).
are semicolons just used by C-family programmers in JavaScript or are they mandatory?
What about the brackets for e.g. new Array()
Dunno....I'm too green. I was under the assumption that semicolons were necessary syntax inside functions, but not in global code.

Thank you for the help....I'm getting the hang of the js stuff (enough to be dangerous anyway), I'm just still a little confused about how the API specific stuff works.

Anyone know if its possible to pre/append some indicator to the callback string from the API constructor itself? This would greatly ease the need for additional function calls. Or is that just the way it is? It seems to me right now that the only way to get feedback from specific controls is to provide a seperate function call for each controls callback. If there was a way to prepend to it, all callbacks could return to the same function and be parsed there if need be. Or alternatively, is there a way in js to parse what object initiated the function? (I'm guessing the latter is possible and I've answered my own question, I'll go check it out now)

Anybody have some API code they want to share? ;)
http://www.aumhaa.com for Monomod and other m4l goodies.

halley
Posts: 40
Joined: Sun Jun 28, 2009 8:48 am

Re: Solutions for JS scripting in M4L?

Post by halley » Thu Dec 10, 2009 12:01 pm

Have you seen this thread?
on Cycling74 M4L forum:
http://cycling74.com/forums/topic.php?id=22985


There is this code:

Code: Select all

function observe_apc40()
{
	// just move this outside of the function if you want to access it from other functions
	var apc40_view = new Array();

	for (j=0;j<155;j++)
	{
		// numbers of controls can be obtained dinamically with getcount() function
		// here is hardcoded to 155 controls
		//
		// - selects control ("live_set control_surfaces 0 controls " + j)
		// - assigns the selected control to the apc_view[j] array slot 
		// - binds callback function to the selected object 
		//   (AFAIK this can be done only when you call "new LiveAPI()", not later...
		//    in other words you cannot have different callback functions for different observed properties)
		//
		apc40_view[j] = new LiveAPI(this.patcher, callback, "live_set control_surfaces 0 controls " + j);
		if (!apc40_view[j])
		{
			post("no api object\n");
			return;
		}

		// starts observer/monitoring on apc_view[j] control
		apc40_view[j].property = "value";
		
		// you can use this to build other structures in your script... 
		// maybe, for instance, to select which controls you want to observe, which not 
		apc40_view[j].name = apc40_view[j].get("name");

	}
	// sends message "done" to the outlet to inform other objects 
	// that the initialization of apc_view array it's done
	outlet(0,"done");

}

function callback(args)
{
	// customize this based on what you prefer to receive from this js object
	outlet(0,this.name + " " + args[1]);
}

- Assume that apc40 is "control_surfaces 0"
- Put this code in a javascript file.
- Put a "js" object in your patch pointing to "pathToJsFile.js"
- Send the message "observe_apc40". This starts observing all the controls.
Whenever there are changes in the controls these are sent to the callback function.
So, whenever there are changes in the controls these are sent to the outlet.
- Put whatever you need in the patch after the "js" object to do what you want to do with the observed changes.





Regarding the question:
is there a way in js to parse what object initiated the function?

I guess you can use the name property assigned with the instruction:
apc40_view[j].name = apc40_view[j].get("name");
or the id property, if you know the id.
As you can see it the code posted by Andrew_Pask/Pukunui
the keyword "this" inside the callback function, refers to the object
connected with that specific callback.

The selection of which callback to link to specific controls can be done
when selecting/initializing those controls and inserting them in the controls/view array.
A further selection between controls can be done inside the callback function/functions.

That's what I've been doing until now...
But I've decided to wait until there is a bit more documentation... hoping there will be some soon enough.
I love the "reverse engineering attitude" and it surely had brought us good things like
the exposed python liveapi, but this time personally I'd like to spend more time building then exploring...
also because I still have to explore way too much (already documented) stuff. (See Max/MSP/Jitter 'world').

amounra93
Posts: 432
Joined: Sat Jan 24, 2009 8:16 pm
Location: Arcata, CA
Contact:

Re: Solutions for JS scripting in M4L?

Post by amounra93 » Thu Dec 10, 2009 10:54 pm

LOL....I'm so impatient. I figured out how to recursively place API paths into an array last night after muddling about with it a bit, and your post shows me the exact method that I ended up using. It was good practice, though.

this.whatever when referring to the function caller is exactly what I was looking for, and I've got my script working the way I want to now. I'll post it when I work out the details of encoding the data for output.

Thanks to those of you that chimed in, it really did help out :)
http://www.aumhaa.com for Monomod and other m4l goodies.

amounra93
Posts: 432
Joined: Sat Jan 24, 2009 8:16 pm
Location: Arcata, CA
Contact:

Re: Solutions for JS scripting in M4L?

Post by amounra93 » Mon Dec 14, 2009 10:10 pm

So, this is what I ended up with. Testing and advice as you like. Its working pretty well for me.

Inputs go to Launchpad: Grid in takes Matrixctrl style arguments (x y 0-127). Control in (for top row) and Mode in (for side row) just needs button number and value (0-127). Control_surface takes an argument for the api.id, but I haven't really tested this as I don't have two launchpads or anything. Its best to have the launchpad in the first slot, I think. The last inlet takes a bang to initialize the api (for instance, if the launchpad is unplugged and replugged, or if something is not working).

Outlets are the returns from the launchpad, pretty much in reverse. Active_mode reports which mode the Launchpad is in, so you can gate the input to it from whatever you have going on. The last outlet will provide the API's state, i.e. 0 if no control_surface is connected, 1 when it is. This is useful to use instead of loadbang, so that nothing gets sent to the launchpad until the API has loaded and the control_surface is initiated.

This is my very first js, any advice on doing things better, more efficiently, etc. are welcome. I've been testing it for several days, though, and its working fine for me. Let me know how you get along with it.

EDIT::

I should probably mention 2 other things:

The js polls the system at load to see whether a Launchpad is connected, and will grab the first one it finds. I haven't tested this much with other control_surfaces connected, but it should work fine, and has worked so far. If things get messed up, just bang the fifth inlet and it will rebuild the API paths.

The way to get this working, in case your not savvy and still want to play with it, is to copy the following script, create a js in Max, double click on it so it opens in an editor window, paste the following code into it, save it in your Max search path, and then you can open the js (whatever you named it) just by creating a Max js and naming it whatever_you_named_it.js.

Cheers :)

Code: Select all

 autowatch = 1

inlets = 5;
outlets  = 5;
setinletassist(0, "grid input");
setinletassist(1, "control input");
setinletassist(2, "mode input");
setinletassist(3, "control surface assignment");
setinletassist(4, "rebuild api");
setoutletassist(0, "grid output");
setoutletassist(1, "control output");
setoutletassist(2, "mode output");
setoutletassist(3, "active mode");
setoutletassist(4, "api load state");

cs = "0"  
is_loaded="0"
var check = new Task(check_on_api);
check.interval = 2000; 

function check_on_api()
{
    post("check_on_api cs: ", cs, "\n");
    post ("is_loaded", is_loaded, "\n");
    if (is_loaded="0")
    {
        if (cs>"6")
        {
            cs="0";
        }
        api = new LiveAPI(this.patcher, "control_surfaces", cs);
        post("control_surfaces id is: ", api.id, "\n");
        post("control_surfaces type is :", api.type, "\n");
        if (api.type!=("Launchpad"))
        {
            cs=(cs=++cs);
            post("control_surface is not loaded yet, next check control_surfaces: ", cs, "\n");
            outlet(4, 0);              
        }
        else
        {
            post("control_surface has now loaded\n");
            is_loaded="1";
            post ("is_loaded", is_loaded, "\n");
            check.cancel();
            assign_api();
        }
    }      
}

function assign_api()
{
    controls=new Array;
    modes=new Array;
    grid=new LiveAPI(this.patcher, cb_grid, "control_surfaces", cs, "controls", 1);
    grid.property = "value";
    var makecontrols=0;
    for (makecontrols=0;makecontrols<=7;makecontrols++)
    {
        var controlid=(makecontrols+67);
        controls[makecontrols]=new LiveAPI(this.patcher, cb_controls, "control_surfaces", cs, "controls", (controlid));
        controls[makecontrols].property = "value";
    } 
    var makemodes=0;
    for (makemodes=0;makemodes<=7;makemodes++)
    {
        var modeid=(makemodes+75);
        modes[makemodes]=new LiveAPI(this.patcher, cb_modes, "control_surfaces", cs, "controls", (modeid));
        modes[makemodes].property = "value";
    }
    active_mode=new LiveAPI(this.patcher, cb_active_mode, "control_surfaces", cs, "components", 0);
    active_mode.property = "mode_index";
    post("done_building_control_array \n");
    outlet(4, 1);
}

function list(b, v, c)
{
    if (inlet==0)
    {
        post("receive grid: ", b, v, c, "\n");
        grid.call("send_value", b, v, c);
    }
    else if(inlet==1)
    {
        var b=(b);
        post("receive controls: ", b, v, "\n");
        controls[b].call("send_value", v); 
    }
    else if(inlet==2)
    {
        post("receive mode: ", b, v,"\n");
        modes[b].call("send_value", v);
    }
    else if(inlet==3)
    {
        cs=(b);
        post("control_surface_number :", b, "\n"); 
        assign_api();
    }           
    else if(inlet==4)
    {
        if (b==("1"));
        {
            post("control_surface initialize\n");
            assign_api();
        }
    }    
}

function cb_grid(args)
{
    var val=(args);
    if (val[0]==("value"))
    {
        if (val[1]!==("bang"))
        {
            post("send grid", args[2], args[3], args[1], "\n");
            outlet(0, args[2], args[3], args[1]);
        }
    }
}
function cb_controls(args)
{
    var val=(args);
    if (val[0]==("value"))
    {
        if (val[1]!==("bang"))
        {
            var id1=(parseInt(this.path.slice(28)))-67;
            post("send control", id1, val[1], "\n");
            outlet(1, id1, val[1]);
        }
    }
}
function cb_modes(args)
{
    var val=(args);
    if (val[0]==("value"))
    {
        if (val[1]!==("bang"))
        {
            var id1=(parseInt(this.path.slice(28)))-75;
            post("send mode ", id1, val[1], "\n");
            outlet(2, id1, val[1]);
        }
    }
}
function cb_active_mode(args)
{
    var val=(args);
    post("active_mode ",  val[1], "\n");
    outlet(3, val[1]);
}
function loadbang()
{
    check.repeat();
}
Last edited by amounra93 on Tue Dec 15, 2009 12:44 pm, edited 2 times in total.
http://www.aumhaa.com for Monomod and other m4l goodies.

amounra93
Posts: 432
Joined: Sat Jan 24, 2009 8:16 pm
Location: Arcata, CA
Contact:

Re: Solutions for JS scripting in M4L?

Post by amounra93 » Tue Dec 15, 2009 12:11 pm

Consider this beta? (LOL) I will comment the script when I get finished with what I'm working on, in case anyone wants to know what is going on. There were a few major problems I discovered tonight, after using it in a few patches. Corrected, it should whir along fine now.

Slimmed down the api constructors called (by 63 hehe). It does the same thing, should require less overhead. Thanks to Tone Deft for posting his patch, which set me straight on one important thing.

Also, to rebuild the api links, send inlet 5 an int of '1', not a bang.

Edited the original post, just copy and paste.

I'll continue to edit the original and post a new reply when I make updates.

Cheers :)
http://www.aumhaa.com for Monomod and other m4l goodies.

Post Reply