Listening to ButtonElement and ButtonMatrixElement in M4L

Questions and discussion about building and using Max for Live devices
Post Reply
mat101
Posts: 6
Joined: Mon Feb 20, 2006 2:23 pm

Listening to ButtonElement and ButtonMatrixElement in M4L

Post by mat101 » Tue Aug 23, 2016 10:58 pm

Hello,

I am stuck trying to write a Max4Live patch which listens to ControlSurface components in the Live Object Model, specifically ButtonElement and ButtonMatrixElement.

I am using the 'js' object to run some JavaScript code which creates LiveAPI objects on those elements to observe their "value" property, similarly to how it is described in https://cycling74.com/forums/topic/how- ... 7zJlpN96Rt

Code: Select all

function test() {
  var api = new LiveAPI(sample_callback, "control_surfaces 1");
  if (!api) {
    post("no api object\n");
    return;
  }

  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(\"controls\")", api.getcount("controls"), "\n");

  post("Iterating over control list\n");
  var length = api.getcount("controls");
  for(var i=0; i<length; i++){
    api.path = "control_surfaces 1 controls " + i;
	
    post("api.id is", api.id, "\n");
    post("object name is", api.get("name"), "\n");
    post("api.type is", api.type, "\n");
    post("api.children are", api.children, "\n");
    
    // Find objects by name
    var name = api.get("name");

    // Find the button matrix element
    if(name == "Button_Matrix"){
      post("Found Button Matrix\n");
      var matrixListener = new LiveAPI(matrixListenerCallback);
      matrixListener.path = api.path;
      matrixListener.property = "value";
    }

    // Find a single button
    if(name == "7_Clip_7_Button"){
      post("Found Button 7_Clip_7\n");
      post("Button's api.type is", api.type, "\n");

      var buttonListener = new LiveAPI(buttonListenerCallback);
      buttonListener.path = api.path;
      buttonListener.property = "value";
    }
  }
}

function sample_callback(args) {
    // nothing
}

function matrixListenerCallback(args){
  post("matrixListenerCallback:", args, "\n");
}

function buttonListenerCallback(args){
  post("buttonListenerCallback:", args, "\n");
}

// Called on bang message being received
function bang() {
	post("Initial bang()\n");
	test();
}


When a bang message is sent to the js object running the script it successfully retrieves the objects, and creates LiveAPI instances whose callback triggers with the initial bang as the value. The Max Console shows the following:

Code: Select all

js: api.id is  -90 
js: object name is  7_Clip_7_Button 
js: api.type is  ButtonElement 
js: api.children are  canonical_parent 
js: Found Button 7_Clip_7
js: Button's api.type is  ButtonElement 
js: buttonListenerCallback:  id  -90 
js: buttonListenerCallback:  value  bang 
js: call send_value(  0  )  id  0 
js: api.id is  -91 
js: object name is  Button_Matrix 
js: api.type is  ButtonMatrixElement 
js: api.children are  canonical_parent 
js: Found Button Matrix
js: call height result is   8 
js: matrixListenerCallback:  id  -91 
js: matrixListenerCallback:  value  bang 
However, button presses are not detected. The callbacks are only triggered once, with the bang value.
Am I observing the wrong property?

What is the correct way to listen to control surface elements in max4live?

Thanks a lot for your help!

/Mario

mat101
Posts: 6
Joined: Mon Feb 20, 2006 2:23 pm

Re: Listening to ButtonElement and ButtonMatrixElement in M4L

Post by mat101 » Wed Aug 24, 2016 12:23 am

I have now found this post which partially answers my question:
https://cycling74.com/forums/topic/list ... 7zXXpN96Rs

I have noticed that the control values can be observed as long as the SessionComponent using them is enabled. Disabling the session component by calling set_enabled(0) on it also has the effect of entirely disabling all the buttons it uses.

So, now I have a new question:
how can I disable the "session ring" from M4L / JavaScript and have its button controls still send their values?

Thanks!

/Mario

S4racen
Posts: 5207
Joined: Fri Aug 24, 2007 4:08 pm
Location: Dunstable
Contact:

Re: Listening to ButtonElement and ButtonMatrixElement in M4L

Post by S4racen » Wed Aug 24, 2016 6:33 am

In later controllers you'll find a grab and release call for the controls...

No idea how to do it in JS, the whole of the control surface stuff is pretty undocumented!

Cheers
D

19oclock
Posts: 54
Joined: Thu Aug 21, 2014 5:40 pm

Re: Listening to ButtonElement and ButtonMatrixElement in M4L

Post by 19oclock » Thu Sep 01, 2016 6:22 am

So my initial response is that maybe assigning path using the .path property may behave funny as the LiveAPI object returns it as a string with quotes around it. So, if your path is...

Code: Select all

control_surfaces 0 controls 92
...then using the .path property actually returns...

Code: Select all

"control_surfaces 0 controls 92"
It's a subtle difference, but may be throwing things off. For fun, see if you can instantiate the LiveAPI object with the path as you use it in your code. So, you would hammer out...

Code: Select all

var buttonListener = new LiveAPI(buttonListenerCallback, "control_surfaces 1 controls " + i);
or my favorite way of handling this one...

Code: Select all

var buttonListener = new LiveAPI(buttonListenerCallback);
buttonListener.id = api.id;
The ID #'s are much more effective, I feel.

Next part of the diagnosis is maybe refining your callback code to keep an eye on what arguments are being passed. You can see by the log created that the callback is triggered at the initial instantiation of the LiveAPI objects.

Code: Select all

js: api.id is  -91
This happens when the object is instantiated: the callback is triggered with the assignment of the object ID as it's default listened-to property. Once you switch this by assigning a .property value the callback is called upon those changes. I always insert a little check in my callbacks to prevent ID's from triggering it...

Code: Select all

function MFXCallback(args) {
	if (args[0] == "value") {
		switch (args[1]) {
			case 127:
                                // Do something related to a button press down.
				break;
			case 0:
                                // Do something related to a button being released.
				break;
			default:
                                // If somehow someone figures out how to yield something other than pressed or released.
				break;
		}	
	}
}
I check the property that triggered the callback. This is by checking args[0]. So long as we have a "value" being passed, we proceed. I vote you build something like this into the callback and then try hammering the button on the controller to see what the LiveAPI objects receive.

I can't recall ever receiving a "bang" message as a value passed from a button press. I am working on the APC40 and APC40 MKII controllers where these come across as 127 (pressed) or 0 (released) values. What would be in the inverse of "bang" to denote a button being released? What controller is this? I think that something simple is misbehaving as my initial reaction.

This stuff is fully documented in the Python code. It's decompilable/has been decompiled so I use it as a reference when working on controllers in the context of Live (https://github.com/gluon/AbletonLive9_RemoteScripts). If you have any questions on this stuff feel free to hit me up at drew@drewreges.com. Code for my MKII work is on my website, too.

Post Reply