The Live Object Model LOM is the key to advanced m4l.
The Live Object Model LOM is the key to advanced m4l.
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!]
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.
Re: The Live Object Model LOM is the key to advanced m4l.
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
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");
}
Re: The Live Object Model LOM is the key to advanced m4l.
Wow!
Top-notch, I'm so on this.
As a style note, in JS I tend to prefer to raise an exceptioninstead of a pattern like: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. )
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!
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');
Code: Select all
if (badness) {
post('ERROR: description of badness');
return;
}
(And also because it's less code. )
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!
Re: The Live Object Model LOM is the key to advanced m4l.
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
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
____________________________________________________________________________________________________
____________________________________________________________________________________________________
art + teaching/consulting
ableton certified trainer
____________________________________________________________________________________________________
-
- Posts: 95
- Joined: Tue Sep 01, 2009 4:22 pm
Re: The Live Object Model LOM is the key to advanced m4l.
TomSwirly,
I am *so* looking forward to the js scripts you will be writing...
[ddg]
I am *so* looking forward to the js scripts you will be writing...
[ddg]
Re: The Live Object Model LOM is the key to advanced m4l.
so am I!DarwinGrosse wrote:TomSwirly,
I am *so* looking forward to the js scripts you will be writing...
[ddg]
Julien Bayle
____________________________________________________________________________________________________
art + teaching/consulting
ableton certified trainer
____________________________________________________________________________________________________
____________________________________________________________________________________________________
art + teaching/consulting
ableton certified trainer
____________________________________________________________________________________________________
Re: The Live Object Model LOM is the key to advanced m4l.
Blush. Well, the proof of the pudding is in the eating. 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...
Right now I'm trying to figure out why this new Launchpad isn't lighting up and flashing madly...
Re: The Live Object Model LOM is the key to advanced m4l.
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:
Is there any particular advantage to using any of these over the others?
Will any of them be dropped?
Thanks,
jml
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");
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.
Re: The Live Object Model LOM is the key to advanced m4l.
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
http://forum.ableton.com/viewtopic.php?f=35&t=130558
thx
Re: The Live Object Model LOM is the key to advanced m4l.
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
i haven't tested this yet... but that could be the key to answering both of our questions.
jml
Re: The Live Object Model LOM is the key to advanced m4l.
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).
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).
Re: The Live Object Model LOM is the key to advanced m4l.
In regard to this.
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
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");
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
Re: The Live Object Model LOM is the key to advanced m4l.
This makes a lot of sense and sheds light on why one would benefit from more than one LiveAPI() instance.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.
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
Re: The Live Object Model LOM is the key to advanced m4l.
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.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.