The Live Object Model LOM is the key to advanced m4l.

Learn about building and using Max for Live devices.
TomSwirly
Posts: 109
Joined: Wed Apr 30, 2008 7:55 pm

The Live Object Model LOM is the key to advanced m4l.

Post by TomSwirly » Thu Nov 05, 2009 11:37 pm

At least so I believe...

Look at the page in the m4l documentation called LOM - The Live Object Model.

It seems to me that you only get THREE things in an m4l device:

1. MIDI in and out
2. audio in and out
3. access to the LOM

So if you want to do meta things, the only way to do it is through the LOM.


I'm about to write a program to dump the entire contents of the LOM to a text file so you can read it. Stay tuned!

And if any of you know the difference between "child" and "children" (referring to the "getinfo" message to live.object), do let me know.


[Moderators/Ableton people, is there a way to get a copy of that page (also called m4l_live_object_model.maxref.xml) as HTML so we can discuss it better? Frankly, is there any way to get ALL that documentation up in HTML - so we can discuss it?

[After a decade with that Max Help Viewer, I still hate it - it doesn't even understand page up/down or, worse, cut and paste!]
Last edited by TomSwirly on Fri Nov 06, 2009 12:10 am, edited 1 time in total.

pukunui
Posts: 405
Joined: Thu Jan 29, 2009 10:26 pm
Location: Los Angeles

Re: The Live Object Model LOM is the key to advanced m4l.

Post by pukunui » Thu Nov 05, 2009 11:54 pm

The LOM is all accessible from js via the LiveAPI object

We'll be documenting this more, but just because you're a js junkie here's a sneak peek

It looks a little like

var api = new LiveAPI(this.patcher, [mode: 0/1], [callback_function], ["initial path"]);

mode 0 (default) means "id follows object", 1 means "id follows path". We've discovered some (Live) bugs in the latter mode, though, so don't worry about that right now.

callback_function is an optional function to receive notifications (id, observed properties).

"initial_path" is a string or list or array forming a valid path, such as you would send to live.path using the 'path' or 'goto' methods.

The object has the following properties:
id (r/o, id of the object referred to by the path)
path (path)
children (children of the object)
mode (id follows object or id follows path)
type (type of the object)
info (info string of the object)
property (observed property)
proptype (type of observed property)

and the following methods:
getcount "thing to count", eg. api.getcount("tracks");
goto "path"
get "object_property"
set "object_property" value
call "object_function" args



Here's a sample js for you to try

Code: Select all

autowatch = 1;

// THIS SCRIPT ASSUMES THAT YOU ARE RUNNING IT FROM THE FIRST DEVICE OF TRACK 1 (the 2nd track from the left)
// IT'S A WHOLE BUNCH OF USELESS RANDOM TESTS OF THE LiveAPI OBJECT, BUT SHOULD GIVE YOU AN IDEA OF HOW TO USE IT.

function test()
{
	var api = new LiveAPI(this.patcher, test_callback);
	if (!api) {
		post("no api object\n");
		return;
	} else post("test: new api", api, "\n");
	
	api.name = "_test_"

	post("== first test: no path (root)\n");
	post("api.mode", api.mode ? "follows path" : "follows object", "\n");
	post("api.id is", api.id, "\n");
	post("api.path is", api.path, "\n");
	post("api.children are", api.children, "\n");
	post("api.getcount(\"control_surfaces\")", api.getcount("control_surfaces"), "\n");
	post("\n");
	
	post("== second test: string path\n");
	api.path = "live_set tracks 0"
	post("api.mode", api.mode ? "follows path" : "follows object", "\n");
	post("api.id is", api.id, "\n");
	post("api.path is", api.path, "\n");
	post("api.children are", api.children, "\n");
	post("api.getcount(\"devices\")", api.getcount("devices"), "\n");
	post("\n");

	post("== third test: array path\n");
	api.path = ["live_set", "tracks", "1"];
	post("api.mode", api.mode ? "follows path" : "follows object", "\n");
	post("api.id is", api.id, "\n");
	post("api.path is", api.path, "\n");
	post("api.children are", api.children, "\n");
	post("api.getcount(\"devices\")", api.getcount("devices"), "\n");
	post("api.type is", api.type, "\n");
	post("api.info is", api.info, "\n");
	post();

	post("== fourth test\n");
	api.path = ["live_set tracks 1 devices 0"];
	post("api.mode", api.mode ? "follows path" : "follows object", "\n");
	post("api.id is", api.id, "\n");
	post("api.path is", api.path, "\n");
	post("api.children are", api.children, "\n");
	post("api.getcount(\"parameters\")", api.getcount("parameters"), "\n");
	post("api.type is", api.type, "\n");
	post("api.info is", api.info, "\n");
	post();

	post("== fifth test: setting an observer\n");
	api.path = "live_set";
	post("api.id is", api.id, "\n");
	post("api.path is", api.path, "\n");
	post("api.type is", api.type, "\n");
	api.property = "tracks";
	post("api.property is", api.property, "\n");
	post();

	api.property = ""; // avoid an error -- need to investigate this
	api.goto("live_set tracks 1");
	post("api.id is", api.id, "\n");
	post("api.path is", api.path, "\n");
	post("api.type is", api.type, "\n");
	api.name = "mutewatcher";
	api.property = "mute";
	post("api.property is", api.property, "\n");
	post();
/*
	post("== last test: changing mode\n");
	api.mode = 1;
	post("api.mode", api.mode ? "follows path" : "follows object", "\n");
	api.mode = 0;
	post("api.mode", api.mode ? "follows path" : "follows object", "\n");
*/	
}

function isArray(obj) {
   return (obj.constructor == Array)
}

function test2() // assumes a live.dial object or similar in the device running the script
{
	var parameters = new LiveAPI(this.patcher, test_callback, "live_set tracks 1 devices 0 parameters 1");
	parameters.name = "_test2_";
	
	post("parameters.path is", parameters.path, "\n");
	
	post("getting parameter name with 'get'\n");
	var name = parameters.get("name");
	post(name,"is",name.length,"long.\n");
	post("typeof name is",typeof name,"\n");
	if (isArray(name)) post("name is an array!\n");

	post("getting parameter name with 'getstring'\n");
	name = parameters.getstring("name");
	post(name,"is",name.length,"long.\n");
	post("typeof name is",typeof name,"\n");
	if (isArray(name)) post("name is an array!\n");
	
	post("getting parameter max with 'get'\n");
	var max = parameters.get("max");
	post(max,"is",max.length,"long.\n");
	post("typeof max is",typeof max,"\n");
	if (isArray(max)) post("max is an array!\n");

	post("getting parameter max with 'getstring'\n");
	max = parameters.getstring("max");
	post(max,"is",max.length,"long.\n");
	post("typeof max is",typeof max,"\n");
	if (isArray(max)) post("max is an array!\n");
	
	parameters.path = "live_set";
	post("parameters.path is", parameters.path, "\n");
	post("getcount is", parameters.getcount("tracks"), "(", typeof parameters.getcount(), ")\n");
	post("gettype is", parameters.type, "(", typeof parameters.type, ")\n");
	post("info is", parameters.info, "(", typeof parameters.info, ")\n");	
	post("children are", parameters.children, "(", typeof parameters.children, ")\n");
}

function test3()
{
	var api = new LiveAPI(this.patcher);

	api.path = "live_set tracks 1";
	var ct = api.getcount("devices");
	post(api.path, "has", ct, "devices\n");

	api.path = "live_set tracks 1 devices 0";
	ct = api.getcount("parameters");
	post(api.path, "has", ct, "parameters\n");

	if (ct) {
		api.path = "live_set tracks 1 devices 0 parameters 0"; /* parameter 0 is always "Device On" */

		var name = api.get("name"); /* note 'get' */
		post(name,"is",name.length,"long.\n");
		post("typeof name is",typeof name,"\n");

		name = api.getstring("name"); /* note 'getstring' */
		post(name,"is",name.length,"long.\n");
		post("typeof name is",typeof name,"\n");

		api.set("value 0"); /* also 'api.set("value", 0);' -- turn the device off */
		post("Boop! YOU JUST DISABLED THE DEVICE!\n");
		post("YOU WILL HAVE TO MANUALLY RE-ENABLE IT BEFORE DOING ANYTHING ELSE WITH IT.\n");
	}
}

function start()
{
	var api = new LiveAPI(this.patcher, test_callback, "live_set");
	if (!api) {
		post("no api object\n");
		return;
	} else post("start: new api", api, "\n");
	api.call("start_playing");
}

function stop()
{
	var api = new LiveAPI(this.patcher, test_callback, "live_set");
	if (!api) {
		post("no api object\n");
		return;
	} else post("stop: new api", api, "\n");
	api.call("stop_playing");
}

function mute()
{
	var api = new LiveAPI(this.patcher, "live_set", "tracks", 1);
	if (!api) {
		post("no api object\n");
		return;
	} else post("mute: new api", api, "\n");
	api.set("mute", 1);
	post("set mute of", api.path, "to", api.get("mute"));
	post();
}

function unmute()
{
	var api = new LiveAPI(this.patcher, "live_set", "tracks", 1);
	if (!api) {
		post("no api object\n");
		return;
	} else post("unmute: new api", api, "\n");
	api.set("mute 0");
	post("set mute of", api.path, "to", api.get("mute"));
	post();
}

function test_callback(args)
{
	post("callback arrived from", this, ":", args, "\n");
	if (this.name)
		post("name is", this.name, "\n");
}

TomSwirly
Posts: 109
Joined: Wed Apr 30, 2008 7:55 pm

Re: The Live Object Model LOM is the key to advanced m4l.

Post by TomSwirly » Fri Nov 06, 2009 12:18 am

Wow!

Top-notch, I'm so on this.

As a style note, in JS I tend to prefer to raise an exception

Code: Select all

if (badness)
  throw new Error('description of badness');
instead of a pattern like:

Code: Select all

if (badness) {
  post('ERROR: description of badness');
  return;
}
because raising the exception guarantees that your code won't go on and do something wrong later - and you can always catch the exception if you need to continue.

(And also because it's less code. :-D)

Oh, and one more JS note - I really like your "IsArray" function - I have one that's left over from the browser world that's more complex than that but I think I'll dump mine for yours within Max's Javascript.

If you care :-) testing array.constructor == Array doesn't work in general in every browser as I've found to my chagrin... there are things that are derived from Array and thus have a different constructor!

julienb
Posts: 1816
Joined: Sat Oct 29, 2005 1:15 pm
Location: France
Contact:

Re: The Live Object Model LOM is the key to advanced m4l.

Post by julienb » Fri Nov 06, 2009 7:44 pm

I'm interested in JS and especially by what you titled "advanced m4L"
I'm working on a big interface patch between my protodeck controller and Live.
I'm almost ok.
but it needs to be improved and there are a couple of things I need a lot (especially send/receive multiple channel/device from/to a patch)

so I'll check JS and further tips/tricks about advanced m4L too :)
Julien Bayle
____________________________________________________________________________________________________

art + teaching/consulting
ableton certified trainer
____________________________________________________________________________________________________

DarwinGrosse
Posts: 95
Joined: Tue Sep 01, 2009 4:22 pm

Re: The Live Object Model LOM is the key to advanced m4l.

Post by DarwinGrosse » Sat Nov 07, 2009 5:25 am

TomSwirly,

I am *so* looking forward to the js scripts you will be writing...

[ddg]

julienb
Posts: 1816
Joined: Sat Oct 29, 2005 1:15 pm
Location: France
Contact:

Re: The Live Object Model LOM is the key to advanced m4l.

Post by julienb » Sat Nov 07, 2009 8:34 am

DarwinGrosse wrote:TomSwirly,

I am *so* looking forward to the js scripts you will be writing...

[ddg]
so am I!
Julien Bayle
____________________________________________________________________________________________________

art + teaching/consulting
ableton certified trainer
____________________________________________________________________________________________________

TomSwirly
Posts: 109
Joined: Wed Apr 30, 2008 7:55 pm

Re: The Live Object Model LOM is the key to advanced m4l.

Post by TomSwirly » Sat Nov 07, 2009 6:30 pm

Blush. Well, the proof of the pudding is in the eating. :-D Which means, wait till I actually have something to show.

Right now I'm trying to figure out why this new Launchpad isn't lighting up and flashing madly... ;-)

jayemell
Posts: 95
Joined: Wed Nov 18, 2009 6:05 pm

Re: The Live Object Model LOM is the key to advanced m4l.

Post by jayemell » Mon Dec 07, 2009 1:31 am

I had a general question about the method by which you are making these calls to the API, Andrew.

You gave the following 6 examples:

Code: Select all

1. api.path = "live_set tracks";
2. api.path = ["live_set tracks"];
3. api.path = ["live_set, "tracks"];

//this is obviously more of an init phase approach
4. var api = new LiveAPI(this.patcher, test_callback, "live_set tracks"); 
5. var api = new LiveAPI(this.patcher, test_callback, "live_set", "tracks");

6. api.goto("live_set tracks");
Is there any particular advantage to using any of these over the others?
Will any of them be dropped?

Thanks,
jml
Last edited by jayemell on Tue Dec 08, 2009 5:14 pm, edited 1 time in total.

mdk
Posts: 914
Joined: Sun Jul 31, 2005 3:51 pm
Location: Skopje, Macedonia
Contact:

Re: The Live Object Model LOM is the key to advanced m4l.

Post by mdk » Mon Dec 07, 2009 9:35 am

and while we're talking javascript api access, i have a few unanswered questions over here :

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

thx :)
Pr0k Records - Bandcamp Facebook Twitter

jayemell
Posts: 95
Joined: Wed Nov 18, 2009 6:05 pm

Re: The Live Object Model LOM is the key to advanced m4l.

Post by jayemell » Mon Dec 07, 2009 4:21 pm

is it possible that the difference in the way you instantiate the LOM object yields differing return types?
i haven't tested this yet... but that could be the key to answering both of our questions.

jml

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

Re: The Live Object Model LOM is the key to advanced m4l.

Post by halley » Tue Dec 08, 2009 2:25 pm

IMHO it's not necessary a matter of advantage...

assignments to api.path
and call to api.goto()
still need the creation of the LiveAPI object assigned to "api" variable.

It has been written in the forum somewhere that calls relative to path are slow...
what's not clear it's slow... but compared to what exactly? To accessing by id?
Is this the same difference from accessing an array by int instead of accessing a dictionary/associative data structure (hash or string based).
If that's the difference... given that the song surely contains a lot of objects but I guess they rarely grow beyond a few hundreds,
is the difference between path and id access so much different in terms of performance?

What's not clear also is what exactly happens under the hood.
In OO languages usually the instruction "new" creates a new object, the type of the object generally depends on the class constructor called after the new keyword.
According to this idea, the instruction "new LiveAPI()" should create a new object of type LiveAPI...
instead based on the path you give, type property of the created object returns different values.
So, what exactly is LiveAPI? is it a sort of Factory or what?

And... given the sequence:
- assign an object returned by new LiveAPI() to a variable
- assign a different path or id to it
the result is that the variable/object type changes.

Does this mean that:
- a new object is created
- a new instance is obtained
- a cast operation is performed
- there is a tree of objects with song as root. (sort of tree of singletons, multitons) and changing the path just means that you 'point' to a new object.

I think that the last hypothesis is the most likely, but if that's the case:
- wouldn't be better just to access various elements in the tree directly. Maybe using a dotted notation?
isn't the instruction "new LiveAPI()" somehow misleading?
- still assuming that new LiveAPI() and/or path/id assignment just move you inside the song tree... what's faster/better to use. are there any best practices?

In general the only assumption I made in the few scripts I tried to write,
and that should be a safe assumption regardless of the implementation details,
is that if you have to use one specific path very often then
it's better to create an object out of it
(i.e. call new LiveAPI and assign the result to a variable)
and then continue using that object.

One last thing.
In the new LiveAPI() call it's possible to pass a callback function that is called every time an observed property changes,
but also some other times (if I recall it right it gets called also when the creation itself it's finished or something like that...)
What if I want to create an object and want to start an observation after a while.
Is there a way to assign callback functions to an object after the creation? Maybe assigning a different function based on the property
observed... One callback function for changes in name, one callback functions for changes in property x, another for changes in property m.



As already pointed out by others in Cycling and Ableton Forums
this is the first version of M4L and there are surely some things missing and
problems to be solved.
Personally I have no problems with that because the things to do with this version
are plenty enough from my point of view, and I think this will remain true with newer version in the future.
I would only like to ask, hoping this doesn't offend or bother anyone,
if it's possible to have some more info/documentation about the way to approach
a few things.
There's no info regarding Control_Surfaces, Controls and Components.
The quantity of js code is very limited, just a couple of scripts in the forums but
nothing more and no clear idea of the differences between how to treat objects and calls
in the patches and/or inside javascript code. (For instance in the patches you use live.observer,
live.path, etc. instead in js code there is only the LiveAPI() object but it creates objects of
other types. There are a few words in the documentation about this but no deep explanation and/or
comparison of the patch vs. code approaches).

pukunui
Posts: 405
Joined: Thu Jan 29, 2009 10:26 pm
Location: Los Angeles

Re: The Live Object Model LOM is the key to advanced m4l.

Post by pukunui » Tue Dec 08, 2009 4:17 pm

In regard to this.

Code: Select all

1. api.path = "live_set tracks";
2. api.path = ["live_set tracks"];
3. api.path = ["live_set, "tracks"];

//this is obviously more of an init phase approach
4. var api = new LiveAPI(this.patcher, test_callback, "live_set tracks"); 
5. var api = new LiveAPI(this.patcher, test_callback, "live_set", "tracks");

5. api.goto("live_set tracks");
I was a little afraid of this when I first posted this beta code.

Jeremy had to rewrite the code so that a path had to be a proper string. So only 1,4,5 and 5? I think you meant 6, will work for sure, the others may or may not.

BTW , there's a start at documenting the Live API js stuff now in the docs.

To the questions about the comparison between creating a new object and changing the path of a current one, my understanding is that it amounts to the same thing. In my own LiveAPI js code, once I create them, I tend to leave them where they are. I find it easier to manage things that way. I'll try and get a little more detail about that from the developer of the JS Live API object.

You may only have one callback per LiveAPI() object. So, you may change it if you want, but if you want to observe the "tracks" and "is_playing" properties for live_set at the same time, you'll need 2 live_sets.

-A

jayemell
Posts: 95
Joined: Wed Nov 18, 2009 6:05 pm

Re: The Live Object Model LOM is the key to advanced m4l.

Post by jayemell » Tue Dec 08, 2009 5:24 pm

pukunui wrote:You may only have one callback per LiveAPI() object. So, you may change it if you want, but if you want to observe the "tracks" and "is_playing" properties for live_set at the same time, you'll need 2 live_sets.
This makes a lot of sense and sheds light on why one would benefit from more than one LiveAPI() instance.

Sorry about the typos re: number references. I went back and edited the post.
So yea; 1, 4, 5, and 6 seem to be in line with my understanding of what I already had working and why.

Thanks for the reply.
I think for now I'll stick with a single string to determine paths as it is less typing and easier to interpret (for me, anyway).

best,
jml

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

Re: The Live Object Model LOM is the key to advanced m4l.

Post by amounra93 » Wed Dec 09, 2009 4:41 am

So, can someone point me specifically in the direction of this documentation? I've seen some of the code that pukunui posted earlier, and I've been fiddling a bit, but js is new to me. I haven't installed the newest beta, but I don't see any new stuff here:

http://www.cycling74.com/docs/max5/vign ... clive.html

If there is somewhere else I've missed, I'd love to know where. Also, if there is any more script examples anyone wants to share that contains examples of access to the api, that would be great :) I have a bunch of patches I'm trying to release, but I want to embed the api access in js before I do, if possible. It seems much more efficient.

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


Post Reply