Namespaces

Variants
Actions

Push Programming Oct13 02

From Cycling '74 Wiki
Jump to: navigation, search

Contents

Introduction

The last tutorial was a great (and fairly easy) introduction to working with the Push, but it does suffer from one problem - long load times. In order for the abstractions to provide easy access to all of the Push parameters, Mark's code created a connection to all of the possible parameters. This process can take a long time to load - which can be an irritation in a live performance situation.

In this tutorial, we'll make a new device, and make it in a new way. We will be using Javascript to create a lean-and-mean connection to just the part of the Push that we want (in this case, we will again attack the button matrix), then build a structure around that code.


Here's the code for the PushStepper device: Media:PushStepper.zip

Note that this code contains both an .amxd device file and a .js file (called PushStepperControl.js), which is the heart of the new device. The PushStepper won't work without access to the Javascript file; you can either put it in the same folder as the .amxd file, or you can place it in a common area - such as the Max /patches/ folder.

The Patching Basics

PushProgOct1302-V1a.gif

This device is basically a simple wrapper around a Javascript object (the js object at the bottom left). We interface with the transport to get our transport status and clock feed, and collect some user controls for the device, but most of the good stuff is found in the PushStepperControl.js code file. About the only thing that is really interesting is the "midiStoreAndForward" subpatcher, which holds incoming notes and outputs them whenever it receives a bang from the Javascript. You can peek inside to see it in action.

The Javascript Program

I decided to use Javascript, because it manages some of the complexity in a way that typical (textual) code does best.

There are two sections of the Javascript code that are interesting: the two objects, labeled "Matrix" and "Surface". Let's look at the Surface first.

The Surface object is the high-level object that finds and interfaces with the Push controller. We use a probe into the control_surfaces LiveAPI path in order to identify the control surface labeled "Push" (which is what any Push device will call itself); once this is done, we register access to the button matrix and create some mediation methods to manage interface with the user interface.


Surface.prototype.grabButtonMatrix = function()
{
	if (!this.bmx.grabbed) {
		this.dev.call("grab_control", "id "+ this.bmx.id);
		this.bmx.property = "value";
		this.bmx.grabbed = 1;
		
		this.bmxarr.setLength(savlen);
	}
}


Surface.prototype.releaseButtonMatrix = function()
{
	if (this.bmx.grabbed) {
		this.dev.call("release_control", "id "+ this.bmx.id);
		this.bmx.property = "";
		this.bmx.grabbed = 0;
	}
}

Whenever we grab or release the button matrix, we do it through the grabButtonMatrix and releaseButtonMatrix functions. In addition to performing the grab/release, we also set properties that let us know the current status, and also determine that property that we will be "observing" (in this case, the value). This was actually a difficult piece of logic to get correct, and only with help from Jeremy Burnstein was I able to negotiate the connection correctly.

Finally, when we grab the button matrix, we have to have a way to receive the information. This is done with the Surface.prototype.bmxCallback function, which is called any time that the button matrix values change. I put in an extensive note about the use of 'this' in the callback, and that it refers to the button matrix (not the Surface itself), because of the way that we've instantiated the button matrix interface. Confusing? Sure, but it works, and gives us a chance to do some interesting stuff in the Matrix object.

Matrix Object Info

Much of the Matrix object is about managing arrays. We create three arrays for three purposes:

  • this.mtx manages the toggle state of all the buttons in the button matrix
  • this.dsp is the storage spot for the current display
  • this.sav is the storage spot for the last displayed grid

Why would we want to keep the last state? Because we want to limit the number messages sent to the Push in order to draw its current state (when grabbed), so we only want to update matrix cells that have changed. Whenever something changes on the grid, we call the matrix.prototype.draw method, which runs through the entire display and updates any changed cells. The code for display calculation is actually pretty interesting:

Matrix.prototype.draw = function () {
	var currstep;
	
	// clear the display
	for (var i=0; i<this.x; i++) {
		for (var j=0; j<this.y; j++) {
			currstep = (j * this.x) + i;
			if (currstep == this.step) {
 				this.dsp[i][j] = 6;
			} else if (currstep >= this.len) {
				this.dsp[i][j] = 0;
			} else if (this.mtx[i][j] > 0) {
				this.dsp[i][j] = 3;
			} else {
				this.dsp[i][j] = 1;
			}
			
			if (this.dsp[i][j] != this.sav[i][j]) {
				push.bmx.call("send_value", i, j, this.dsp[i][j]);
				this.sav[i][j] = this.dsp[i][j];
			}
		}
	}
}

The order of drawing is:

  1. if we are looking at the current (transport) step, draw it in red
  2. if we are looking at a cell 'farther out' than the maximum cell, turn it black
  3. if we are looking at a cell that is toggled 'on', turn it bright white
  4. otherwise, turn it dim white

The numbers used for the colors was snagged from the Push.Colors prototype in the contextual menu. It's a great resource for the easiest way to set colors.


Conclusion

That covers most of the unique work done with this patch. If you haven't used the Javascript (js) object, using it to interact with the Push is a very useful way to learn it. While it isn't visual coding, it does provide a very efficient and powerful way to access and maintain the Push's context regardless of other system changes.