How to get two instances of "the same" javascript object in separate top level patchers communicating

Jon Williams's icon

IDK if this information is already documented somewhere, or if the same behavior can be implemented more simply and easily. This is just how I figured out I could implement certain behavior I wanted for a project. Since I struggled so mightily acquiring the details, I thought I would share my solution. I'd also like to know about any better (safer, more flexible maybe) way(s) anyone else has for implementing this kind of behavior. Please reply and share.

DL and save this txt file to a local folder, change the extension from .txt to .js.

patchers.txt
txt 6.79 KB

Save (your "new from clipboard" copy of) the below compressed patch to the same folder as the patchers.js file (saveAs "patcherA.maxpat" maybe)

Max Patch
Copy patch and select New From Clipboard in Max.

Save (your "new from clipboard" copy of) the below compressed patch to the same folder as the "patcherA" Patch and the patchers.js files (saveAs "patcherB.maxpat" maybe)

Max Patch
Copy patch and select New From Clipboard in Max.

Close both "new" Patchers, then reopen one then the other locally from Max File menu. (so they can find the patchers.js file when they load)

PatcherA: (PatcherB is functionally identical to A except B loadbangs"initB")

screengrab of "patcherA" Max patcher window

The two top level patchers each contain a Max js object and each js object is an instance of the code found in the patchers.js file and the two "siloed" instances can still call a method "in" the other instance (after both patches are open).

The "reportOut" messages you pass into "patcherA's" js object appear at "patcherB's" js outlet, and vice versa.

This demo verbosely posts to the max console, but it's for demonstration purposes. Comment out the informational console posts for actual use of the code.

TLDR: Use "introspection" on the Max environment to locate the "Maxobj" (js) objects of concern by "scripting name", then call javascript functions on the "scripting name" objects using the js introspection syntax.

var fnA = "fromA";
if (maxObjB && maxObjB.valid) {
    maxObjB.js[fnA](msg);
    post("reportOut: *msg sent!"); post();

Anyone else think this is useful info?

Jon

TFL's icon

That's nice! I never realized you could traverse patcher windows using `max.frontpatcher.wind.next`.

You can probably simplify your code and make so it can send a message to any given object with a specific scripting name (not only js objects).

To search this object, you could iterate through the windows as you currently do, but then you could use max_patcher.applydeepif()to get all objects with the given scripting name.


...but for simpler code and at the cost of an additional object, I would probably just use messnamed() combined with [receive]. No need to seek for the object(s) at all, since send/receive names are global and can be reached from anywhere.

TFL's icon

I had to try it. The code:

let target_name = null;
let target_obj = null;

function send_to() {
    const args = arrayfromargs(arguments);
    const name_candidate = args.shift();
    if (this.box.varname == name_candidate) return; // Avoid sending messages to itself!
    if (args.length) {
        if (target_name != name_candidate) { // Avoid looking for the same object if it has already been found
            target_name = name_candidate;
            target_obj = null
            let patch = max.frontpatcher;
            let keep_looking = true;
            while (patch && !target_obj) {
                patch.applydeepif(set_target_object, is_target);
                const next_window = patch.wind.next;
                patch = next_window ? next_window.assoc : null;
            }
            if (!target_obj) target_name = null;
        } else {
            if (target_obj) {
                post("Sending to ", target_name, ":", args, '\n');
                target_obj.message(...args);
            }
        }
        
    }
}

function is_target(obj) {
    if (obj.varname == target_name) {
        post("Object found in patcher", obj.patcher.name, '\n');
        return true
    }
    return false
}

function set_target_object(obj) {
    target_obj = obj;
}

// Code to receive messages from other source
function anything() {
    let args = arrayfromargs(messagename, arguments);
    outlet(0, ...args, '\n');
}

It is agnostic to the patcher and objects scripting name: the same code can be used in any number of v8/v8ui without the need of changing the code.

It will search only for a scripting name and will send only to the last applydeepif match of the first patcher window with a candidate (in case you have multiple objects with the same scripting name), but you could easily add filters in is_target() to make sure to target only some specific patch or object.

You could also change the code structure to make the message to be sent to every object with the given scripting name. Basically by storing all matches in a target_objs array.

Example with patch A:

Max Patch
Copy patch and select New From Clipboard in Max.

and patch B:

Max Patch
Copy patch and select New From Clipboard in Max.

The code is embedded in each patcher, and no need to save them as "New from clipboard" already make them top-level patchers.


But again, I don't really see the true benefit of this compared to messnamed() + [receive], or [pattrforward] which can talk to any inlet of any named object (not only pattrized objects) as long as you can identify them with a [pattrmarker] in the same patch (or parent patch).