In a previous post I explained some of the basics on how to use Max for Live to gain more access to the APC40. For example I showed you how you can get control of the APC40 matrix and turn several lights on and off. As one reader pointed out: while that “turn_on” and “turn_off” feature works pretty well, it doesn’t give us access to the APC’s full potential (yet :twisted:).
After all; the lights on the matrix aren’t only green. They are yellow / amber when a clip is present, red when a clip is recording and green when a clip is playing. Heck; sometimes a clip button is even blinking!
So in this post I’m going into some more advanced techniques / theories which I’ve worked out in the past week; I’m going to show the relationship between Components and Controls, demonstrate how the APC (the remote script controlling it) keeps track of all components and how we can get to learn more about the available functions.
APC40 Components and Controls
As I explained last time there is a strict relationship between the components and controls section (which is also displayed in the chart above). Often both sections can refer to the same APC40 buttons or section in order to provide different functionality. So, for example; when I want to change the behaviour of the APC40 button matrix I’d use the components section to disable the matrix and the controls section to actually use it (turn leds on and off, monitor for button presses, etc.). So; controls access the physical hardware and components access the virtual parts which make up the control surface.
Learning about all the available components or controls is relatively easy; we can use the LOM.Navigator for that. But how can we get more information on how to use the several functions provided by all these classes? After all; with functions such as ‘turn_on’ or ‘turn_off’ when dealing with the button matrix (or individual buttons) it’s pretty obvious what that might do. But what about functions such as “send_value”, “send_midi” or even “set_identifier” ?
The hacking process
When I started hacking the APC40 I went all the way; researched a lot and even started trying out the impossible. Why? Just because the documentation tells you how you should use some instructions doesn’t mean you’re necessarily limited to those specific instructions. When it comes to hacking there is one golden rule to keep in mind: “Never take anything for granted.”. For example; the documentation states that the 4 root objects in the Live Object Model (“LOM”) are top-level objects so you obviously can’t go above them. Really?
Then obviously this patch should give us quite a few error messages about not being able to go above a root device.
But guess what; it doesn’t.
When you execute this critter then you’ll get this:
“obj: info description list() -> new list list(sequence) -> new list initialized from sequence’s items“.
Now, in all fairness this doesn’t actually tell us anything. Because when we’re dealing with a “List child” (a child class reference such as “Tracks”, “Scenes” or “Devices”) then obviously something must be there to select the one we need.
If you use the path “live_set tracks” (so without the number) and send a “getinfo” message into a [live.object] then you’ll discover the tuple object: “obj: info description tuple() -> an empty tuple tuple(sequence) -> tuple initialized from sequence’s items If the argument is a tuple\, the return value is the same object.”.
So while this doesn’t prove anything about going “above” root objects it did show me that there was much more to the LOM than I knew so far. If we can go up, then maybe we could also go down…
I set the goal to try and control the button matrix some more. Not the actual buttons themselves; but the part which we can access within the ring indicator. As such I needed the Session_Control component (“control_surfaces x components 0“). When going over the functions and trying to find relationships I noticed 3 similar functions: “track_offset“, “scene_offset” and “set_offsets“.
Moving the indicator around and then calling “track_offset” or “scene offset” showed me that this kept track of the current position of the ring indicator. So it should be safe to assume that set_offsets was used to actually set these to a value of our own. But what parameters would this function need? Sure; the logical assumption is to use the values coming out of the first two functions, but I wanted some confirmation apart from “trial by error” results. Which, as a side note, did show me that my assumption was correct.
If I could use ‘goto up’ to go “above” a root object, maybe I could use the same approach to get to learn more about functions? So I pointed a [live.object] towards Session_Control, then used “goto set_offsets” as a command for the [live.path] object. Followed by a “getinfo” message to the [live.object] again. The result was fascinating:
obj: info id -179
obj: info type instancemethod
obj: info property im_func function
obj: info property im_self PedaledSessionComponent
obj: info done
The first thing to notice is the negative ID value. Second; the im_self property points back towards the same class type as the Session_Control component. And last but not least: im_func, a property of type function? If you’ve used my LOM.navigator to check up on control surfaces before you will have come across such properties before. Could it be…
First I tried to ‘get’ im_func which showed me:
obj: im_func <function set_offsets at 0x04A0C4F0>
Next I used goto again, and guess what:
obj: info id -180
obj: info type function
obj: info property func_closure NoneType
obj: info property func_code code
obj: info property func_defaults NoneType
obj: info property func_dict dict
obj: info property func_doc NoneType
obj: info property func_globals dict
obj: info property func_name str
obj: info done
Playing around with this set of results learned me that the only interesting property was func_code. Heck; the type description itself was already more than enough to get me excited! And it only got better:
obj: info id -181
obj: info type code
obj: info description code(argcount\, nlocals\, stacksize\, flags\, codestring\, constants\, names\, varnames\, filename\, name\, firstlineno\, lnotab[\, freevars[\, cellvars]]) Create a code object. Not for the faint of heart.
obj: info property co_argcount int
obj: info property co_cellvars tuple
obj: info property co_code str
obj: info property co_consts tuple
obj: info property co_filename str
obj: info property co_firstlineno int
obj: info property co_flags int
obj: info property co_freevars tuple
obj: info property co_lnotab str
obj: info property co_name str
obj: info property co_names tuple
obj: info property co_nlocals int
obj: info property co_stacksize int
obj: info property co_varnames tuple
obj: info done
First the bad news; co_code doesn’t do much. Not that surprising considering that we’re basically accessing bytecode (=compiled source code). But I did hit the jackpot with the rest of the properties!
First the obvious: co_argcount. When getting this property I ended up with a value of 3. And since most of Max’ objects start their count with 0 this meant that we had to deal with 4 instead of 2 parameters. But what could those extra 2 parameters be ?
co_varnames gives us some more information on that:
obj: co_varnames self track_offset scene_offset track_increment scene_increment
This makes perfect sense; please welcome our roadmap to discovering the parameters (both required and optional) of every function in the MIDI remote scripts and basically every function in the whole LOM.
What do we have… First ‘self’. Makes sense because you need the name of a function to actually call it. track_offset & scene_offset also make sense; I discovered that order through trial and error. But what about those last 2? If you’re trying to use 4 numerical parameters instead of 2 when calling “set_offsets” then you’ll get errors. So at this time I think these are either optional, or maybe not even int values.
obj: co_names AssertionError _is_linked SessionComponent _perform_offset_change _track_offset _scene_offset len tracks_to_use song scenes _change_offsets
co_names also makes me wonder about a few things. A suggested link to a SessionComponent makes sense, after all; we also have scene components, track strip components, and so on. It seems to describe its function of changing offsets, describes both the track and scene offset. And then it becomes vague; len, song, scenes ?
This is where I ran into a dead end so far. But I think the discovery that you can get this kind of information out of a function is quite exciting!
And some things are simply funny as well as interesting:
obj: co_filename h:JenkinsliveProjectsAppLiveResourcesMIDI Remote Scripts_FrameworkSessionComponent.py
Jenkins live Projects? 😉 Anyone know Mr. or Ms. Jenkins?
So we learned that we can get ‘into’ functions and that these objects all use negative ID values. It is my theory so far that the software side (live_set, live_app, etc) uses positive ID values whereas the hardware (the control surfaces / MIDI remote scripts) all use negative ID values. Which would make sense because what better way to avoid any accidental overlaps?
When I mentioned the first post and some of my findings in the Ableton forum I got some feedback from a couple of guys, amongst which S4Racen. This is the guy behind the excellent M4L patch Isotonik (the official product page can be found here). He mentioned some of the currently known limitations which gave me a new point to focus on: disabling the faders of the APC40.
So I started to check out the faders themselves. Because I was aiming for the hardware I started looking into the controls, ending up with the 0_Volume_Control SliderElement. The functions “message_identifier” and “set_identifier” immediately got my attention due to their possible relation. Diving into these functions as I demonstrated above learned me that ‘set_identifier’ had an argcount of 2, and according to var_names these were: “obj: co_varnames self identifier”.
And then it hit me. Earlier on (with the set_offsets) I got an argcount of 3, the parameters were: self, track_offset, scene_offset and a few more. What if this number was to be picked up literally?
SO in this case; 2 arguments: self and the identifier. Skip self and you end up with 1 single argument.
Unfortunately, even though my theory proved to be true nothing happened. If I used “set_identifier” and set it to 0 then “message_identifier” would also get me the value 0 (previously this value was 7). So although something changed nothing happened. But couldn’t this simply be a same situation as with trying to use “turn_on” and “turn_off” on a button without a led?
Component and control dependencies
And so I turned my attention to the channel strip component, considering that this was the only component with a reasonable chance of accessing the slider (referring to the Channel_Strip_0 component which has the SpecialChanStripComponent type).
Here I noticed the function set_volume_control. When diving into this function, same as before, I learned that the argcount was 2 and that the arguments themselves were: “obj: co_varnames self control”.
So I started experimenting with sending it the value 0. That didn’t work. But looking some more at the options above; could it be that it was actually requiring to be pointed to the physical slider control ?
And how do these controls or objects identify themselves normally? Through the use of ID’s! Every component, control and even functions and properties; they all get ID’s assigned.
SO I started with using “id 0” as function argument and what do you know? The slider was dead. Then I checked the ID number of the 0_Volume_Control SliderElement, fed that into the ‘set_volume_control’ function and all of a sudden the slider responded again!
So not only do these sections use IDs for identification; the actual relationship between components and controls goes a lot deeper than I had realized so far. Sometimes you have to point a component directly to its physical control counterpart in order to activate it again!
And then another user of the Ableton forum, JuanSOLO, mentioned the track and device control section. You know; the section with the knobs and the cool led ring displays…
But that’s something for the next post considering the size of the current one. (yay, I made my very own cliffhanger! ;-)).
Part 2 conclusions
- You can use ‘goto’ with a [live.path] component and actually ‘enter’ functions and some properties in order to learn more about them.
- The control surfaces section uses negative id values whereas the software (live_app, live_set, etc.) uses positive id’s.
- “Entering” a method can teach you a lot about its parameters and other parts.
- Sometimes components and controls have more than a mere relationship; they depend on each other.
The more I experiment with this, the more do I realize that my LOM.Navigator is in serious need of a rehaul. I’m currently spending some of my time to rebuild the patch from the ground up, keeping all the current features intact, and from there on I plan on expanding its functionality quite drastically. I’m already busy with designing the “object navigator” section, this will allow us to investigate functions and properties like I’ve demonstrated above.
Quite frankly; I think that could very well break open the whole control surfaces section a whole lot further.