Creating inlets and outlets automatically

dhjdhjdhj's icon

I haven't found such a feature in Max but am wondering if there's a third party tool that does this.

Basically, I have abstractions that instantiate differently depending on the arguments to the object name. What I'd like to be able to do is select an object (or perhaps even more than one) and have inlets and outlets created automatically for all ports that do not have any explicit connections. Ideally, the port comments would make into the inlet/outlet comments as well.

That would make it extremely easy to then save a new patcher and instantly reuse it as an abstraction without having to create all the inlets/outlets first.

gwsounddsg's icon

You can using scripting with the thispatcher object to create objects and connect them. It takes a bit to figure out how to properly write the patch, but I've used this before and it works like a charm.

dhjdhjdhj's icon

Right....but I was hoping someone had already made such a facility that I could just use....For example, it would be a nice addition to the Max Toolbox

spectre's icon

I did this recently in an attempt to build a modular synth.
Have a look:

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

IO.maxpat:

IO.js:

// IO Objects
var myInlets = null;
var myOutlets = null;
var numInlets = 0;
var numOutlets = 0;

// Inlet coordinates
var inletInsetX = 100;
var inletInsetY = 0;
var inletOffsetX = 50;
var inletOffsetY = 10;
// Outlet coordinates
var outletInsetX = 100;
var outletInsetY = 400;
var outletOffsetX = 50;
var outletOffsetY = 10;

// Creates a given number of inlets and outlets
function createIO(nInlets, nOutlets) {
    clearIO();
    numInlets = nInlets;
    myInlets = [numInlets];
    for(var i=0; i-1; i--) {
            if(myInlets[i] != null) {
                this.patcher.remove(myInlets[i]);
                myInlets[i] = null;
            }
        }
    }
    if(myOutlets != null) {
        for(i=numOutlets-1; i>-1; i--) {
            if(myOutlets[i] != null) {
                this.patcher.remove(myOutlets[i]);
                myOutlets[i] = null;
            }
        }
    }
    numInlets = numOutlets = 0;
    myInlets = null;
    myOutlets = null;
}

spectre's icon

Sorry, took too long to edit that post into something more human-readable. But the patch is there, and just in case the [js] object within it doesn't already contain the script, I've uploaded it here... Just be sure to add a path to it under 'Options->File Preferences' so the [js] object can find it.

2837.IO.js
js
pid's icon

if all you need is functionality, this is extremely easy in max. [poly~]. just fill up with maximum needed ins and outs, then make all your poly~ patches from same template. then in parent the only 'scripting' you need to do is changing the 'comment' attributes on the actual outlets/etc to useful things (such as "unused", or whatever). then dynamically switch poly~ patchers. in audio situations, either live with the (tiny) discontinuity (in its own thread, so everything else safe) or auto ramp your immediate outputs on the changeover.

scripting actual objects and cables takes time and is messy unless done in javascript, and even then massive time taker. i have never come across a general situation where scripting ins/outs was actually useful / advisable / desirable. sometimes i understand it for specific tasks, good recent example is all the peter batchelor modular stuff - but those modules are not designed for 'live' use / changes - only set up etc. they are incredibly slow to instantiate.

2cents.

dhjdhjdhj's icon

I want this as a technique to significantly simply the design of patchers that will have lots of abstractions. It's beneficial for bottom-up development where you create a patcher, make it work and then turn it into an abstraction with inlets and outlets instantly available (and it's much easier to then just delete the ones you don't want). As you build upwards, you have abstractions containing abstractions and some of the inlets and outlets need to just "flow" up or down, so making it trivial to create them would be very valuable.

For top-down development, I'd also love the ability to be able to simply define a new object by giving it a name and a couple of arguments that represent the number of inlets and outlets that the object should have. You could then wire that object up to other objects at a high level and then drill into the object later to implement it.

That's typically how a lot of top-down structured programs get created, you just assume that the lower level pieces you need will be there and you go implement them later. If you were writing with Pascal (say), you would just insert procedure calls with arguments into your top level algorithm and then go back and implement them afterwards. You don't actually want to manually have to go create each stub as you do this, it's a distraction from thinking about the algorithm.

Similarly, having to create the inlets/outlets manually each time is a distraction and automating this would go a long way towards allowing easier top-down development.

I think the combination of these two features would make it much easier to developer larger scale applications in Max.

For what it's worth, I had these features in an iconic programming language I developed a long time (early 90s) and found them to be very effective.

dhjdhjdhj's icon

I think @pid is correct that this has to be done outside of the scripting mechanism. Ideally, you want to be able to just select an object and then press a key sequence that just inserts the precise number of needed inlets and outlets for that object into the patcher.

I suspect the Max Toolkit could be modified to do this but I don't yet have enough knowledge of Max infrastructure to attempt it myself.

Luke Hall's icon

Not quite sure it is what you had in mind but here's a little example of automatically scripting [inlet]s and [outlet]s into place for selected objects. Highlight some objects and press a numeric key to automatically create and connect: 1 for just [inlet]s, 2 for [outlet]s and 3 for both. Inside max javascript has no knowledge of how many inlets and outlets objects have so this code defaults to just the first inlet and/or outlet.

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

I'm working on a way to get the information about the number of inlets and outlets each selected object has and checking to see if they are already connected to another object. Let me know if you think this would be useful because I am easily fascinated and shelve loads of projects because I find something new to work on, maybe if other people give me a push I'll actually get something completed for once. Any feedback at all would be great!

//autoio1.js

var selobj = [];
var io = "inlet";
var offset;
var flag = 1;

function genin() {
io = "inlet";
offset = -27;
bang();
}

function genout() {
io = "outlet";
offset = 2;
bang();
}

function genio() {
genin();
flag = 0;
genout();
flag = 1;
}

function bang() {
if (flag) {
selobj = [];
}
this.patcher.apply(iter);
for (i=0; i
x = selobj[i].rect[0];
y = (selobj[i].rect[1]*(offset0))+offset;
var auto = this.patcher.newdefault(x, y, io);
if (offset > 0) {
this.patcher.connect(selobj[i],0,auto,0);
} else {
this.patcher.connect(auto,0,selobj[i],0);
}
}
}

function iter(obj) {
if (obj.selected) {
selobj.push(obj);
}
return true;
}
// EOF

dhjdhjdhj's icon

I just tried this --- this is essentially exactly what I'm looking for.

Obviously it needs to create an inlet/outlet for all ports, not just the first one.

It would also be convenient if the created inlet/outlet contained the same comment as the original port of the object.

I really appreciate your trying to make this work, I think a lot of people could benefit from this particular feature.

dhjdhjdhj's icon

I was looking in the docs about the javascript stuff --- there seem to be a couple of functions that return the number of inlets or outlets of an object, although I'm not sure how to use it.

See 'inlets' and 'outlets'

Does that help?

Luke Hall's icon

Thanks for having a look but unfortunately javascript doesn't make it that easy, those are for getting and setting the inlet/outlets of the [js] object containing your code, not for other objects in the patch. I've figured out a workaround which is getting very close to completion though, I'll post here it once I have tidied up some of the code.

One thing I meant to ask before: when you say "it would also be convenient if the created inlet/outlet contained the same comment as the original port of the object" do you mean for example that an [inlet] created and patched to a [toggle] would automatically contain "toggle: int Sets Toggle, bang Reverses It" as the comment attribute?

dhjdhjdhj's icon

Wow --- really appreciate your efforts to make this work.

Regarding the comment, that's exactly what I mean.

So, here's an example. I have an abstraction called [GenericVST] which takes two arguments which are the name of the VST and a filename where all the parameters should be stored. For example, I can write

[GenericVST "Kontakt 5" Strings] or
[GenericVST "FM8" DynaPiano]

Now, the GenericVST abstraction of course contains lots of other objects and has several inlets and outlets, the former used to send notes, aftertouch, CC messages and so forth, and the latter are the Audio outputs (left and right).

However, when I am creating "song" patcher, meaning a patcher that represents the configuration (MIDI routing, keyboard layers and splits, program changes to be sent to external MIDI devices), I have a notion of an "instrument". An instrument would be a new abstraction that contains a particular instantiation of a GenericVST. So instead of creating an object called [GenericVST "Kontakt 5" Strings], I want to have an object that's simply called [Strings]. That object is essentially a lightweight facade over the GenericVST but for the most part, all the inlets that appear when I create a GenericVST should exist for Strings with the same meanings.

So basically, my workflow would be to
a) Create a new patcher
b) Insert a [GenericVST "synthname" "patchname"] into it
c) Press a magic key to create inlets and outlets that are connected to the ports of the GenericVST and have the same comments in them.
d) Save that new patcher with the name "patchname"

Now I can very quickly create a collection of instruments using the technique above. By having the inlets/outlets created automatically, the whole mechanims won't take more than about 10 seconds. That's VERY useful.

Luke Hall's icon

See how this new javascript solution works for you.

It loads the conaining patch into the [js] object as JSON, looks up the selected objects to see how many intlets and outlets they have, checks to see if there are already connections made to those outlets and if not scripts the [inlet] and [outlet] objects in to place.

If you add any other objects to the patch you have to re-save it to update and reload the JSON. If you don't it might print an error message because it can't find one of the newly created objects in the uncompressed patch text.

It also gives the scripted objects simple comment descriptions to act as a reminder (however abstractions will all be reported as "patcher" rather than the actual abstraction name).

Let me know how it works and if you have any suggestions for changes then post them here. The obvious one is to have separate commands to work with inlets, outlets or both. It might also be possible to report abstraction names which looks like it would greatly help in your case.

Update: now supports abstraction and [patcher] names in comments.

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

// autoui2.js

var outlets = 2;
var mem;

function loadbang() {
    if(!max.loadbangdisabled) {
        init();
        outlet(1,this.patcher.filepath);
        outlet(1,1);
    }
}

function init() {
    memstr = "";
    data = "";
    maxchars = 800;
    target = this.patcher.filepath;
    f = new File(target,"read");
    f.open();
    if (f.isopen) {
        while(f.position
            memstr+=f.readstring(maxchars);
        }
        f.close();
    } else {
        post("Errorn");
    }
    mem = eval("("+memstr+")");
    post("Loaded!n");
}

var count = 0;
var selobj = [];
var ids = [];
var nins = [];
var nouts = [];
var text = [];

function bang() {
    var mpb = mem.patcher.boxes;
    var mpl = mem.patcher.lines;
    reset();
    this.patcher.apply(iter);
    if (!count) {
        post("Nothing Selectedn");
    } else {
        for (i=0; i
            for (j=0; j
                if(mpb[i].box.patching_rect.slice(0,2).toString() == selobj[j].rect.slice(0,2).toString()) {
                    ids[j] = mpb[i].box.id;
                    nins[j] = [];
                    for (k=0; k
                        nins[j][k] = k;
                    }
                    nouts[j] = [];
                    for (k=0; k
                        nouts[j][k] = k;
                    }
                    if (mpb[i].box.text) {
                        if (mpb[i].box.text.split(" ")[0] == "p") {
                            text[j] = mpb[i].box.text.split(" ")[1];
                        } else {
                            text[j] = mpb[i].box.text.split(" ")[0];
                        }
                    } else {
                        text[j] = "null";
                    }
                }
            }
        }
        for (i=0; i
            for (j=0; j
                if (mpl[j].patchline.destination[0] == ids[i]) {
                    nins[i].splice(mpl[j].patchline.destination[1],1,"null");
                }
                if (mpl[j].patchline.source[0] == ids[i]) {
                    nouts[i].splice(mpl[j].patchline.source[1],1,"null");
                }
            }
        }
        for (i=0; i
            offset = (selobj[i].rect[2]-selobj[i].rect[0]-19)/((nins[i].length-1)||1);
            for (k=0; k
                if (nins[i][k] != "null") {
                    x = Math.round(selobj[i].rect[0]+(offset*nins[i][k]));
                    y = selobj[i].rect[1]-27;
                    var auto = this.patcher.newdefault(x, y, "inlet");
                    this.patcher.connect(auto,0,selobj[i],nins[i][k]);
                    if (text[i] != "null") {
                        comname = text[i];
                    } else {
                        comname = selobj[i].maxclass;
                    }
                    auto.message("comment",comname+": inlet "+nins[i][k]);
                }
            }
            offset = (selobj[i].rect[2]-selobj[i].rect[0]-19)/((nouts[i].length-1)||1);
            for (k=0; k
                if (nouts[i][k] != "null") {
                    x = Math.round(selobj[i].rect[0]+(offset*nouts[i][k]));
                    y = selobj[i].rect[3]+2;
                    var auto = this.patcher.newdefault(x, y, "outlet");
                    this.patcher.connect(selobj[i],nouts[i][k],auto,0);
                    if (text[i] != "null") {
                        comname = text[i];
                    } else {
                        comname = selobj[i].maxclass;
                    }
                    auto.message("comment",comname+": outlet "+nouts[i][k]);
                }
            }
        }
    }
}

function iter(obj) {
    if (obj.selected) {
        selobj.push(obj);
        count++;
    }
    return true;
}

function reset() {
    count = 0;
    selobj = [];
    ids = [];
    nins = [];
    nouts = [];
    text = [];
}

// EOF

dhjdhjdhj's icon

I get the following error messages

js: Error
js: autoio2: Javascript SyntaxError: syntax error, line 29
contextframe: source line: (
js: error calling function loadbang
VSTName: Zebra2
GenericSynth: plug Zebra2
RestartDACMessageSent: bang
js: autoio2: Javascript TypeError: mem is undefined, line 41
js: error calling function bang

pid's icon

works perfectly for me as advertised. the small mistake which needed correcting was the naming of the js file which was messed up (should surely be "autoio2.js").

luke, you really have excelled yourself here. this is even more amazing than your usual amazing javascript posts.

some very quick suggestions:
1. should there be an option for some sort of 'verbose mode on/off'? - at the moment even the right outlets of flonums, for example, are getting their own connections. is there any way of classifying max outlets in order of 'importance'? i doubt it of course!
2. related to that, there needs to be a way to specify whether or not you want also a connection from an already connected object - for example, the left outlet of a flonum in your example is attached to a process in the patch, but we might also want it as an outlet. i guess this is impossible too, and shows the inherent problems in this task.
3. more useful notice: the inlets outlets do not get renumbered if you operate on part of a patch then later operate on another part.
4. also, numbering on in/outlets starting at '0'? - surely we could have a nice '1'-based index system?!

anyway, that is some lovely (and generous) js-ing. i probably will not be using it myself, but i will be admiring the idea and perhaps stealing from it in the future...

Luke Hall's icon

Make sure you save the file first otherwise there will be no JSON representation of the patch on disc for the javascript to find. Then send the "init" message and the max window should print "Loaded!" if it has been successful. Hopefully this will fix the issue and sorry for not making it clear from the start.

Luke Hall's icon

Thanks for the kind words! In response to those suggestions:

1. I've already considered this, however seeing as different people will find different things "important" I decided not to make their minds up for them so I didn't hardcode anything in from the start. It is still pretty easy to delete the objects you don't need. The one option that is available is a personalised preference file that could be loaded and parsed in a similar way (I think the Max Toolbox does this to set key commands). You could specify which UI object's inlets and outlets you want to globally ignore on a user by user basis. This is definitely not impossible to achieve.

2. The original problem was to create inlets and outlets for objects that do not already have explicitly set connections. The workaround here is for you to patch in [trigger] objects and leave one of the inlets or outlets un-connected, then the script will find it and use it.

3. What do you mean by renumbered? Currently the javascript only works in a top level patch, rather than in [patcher] objects where the inlet and outlet objects show their numeric indices. I tried loading the example patch as an abstraction and then using the script on two different sets of objects and the inlets and outlets seemed to update as I'd expect when the file was saved. Can you give me an example?

4. You're right, real-life people count from 1 not 0. After a while iterating through javascript arrays I start counting all wrong.

When the script is updated I'll post it in this thread.

dhjdhjdhj's icon

OK --- two issues

1) The reason I was getting the syntax (sigh) error was because I was doing "New From Clipboard" but I didn't actually SAVE the new patcher, just tried to run it (seemed reasonable thing to do) so there was nothing to read in, hence there was nothing in memstr which is why the error occurs

2) Having fixed that, it works fine on the multislider graphic but if I just instantiate another object (I tried one of my abstractions and a built-in object), and press space bar then I get the following error:

js: autoio2: Javascript TypeError: nins[i] is undefined, line 83
js: error calling function bang

Luke Hall's icon

It's the same issue, every time you add new objects or otherwise modify the patch you will have to resave the patch and initialise the javascript so the JSON is up to date. Otherwise you could be selecting a newly created object in the patcher, but it will not be in the JSON so there's no way of figuring out how many inlets and outlets it has and whether these are connected or not.

The right outlet of the [js] is designed to be connected to [filewatch] which is in turn connected to the "init" message heading back in to the [js] object so that, upon loading, it watches out for changes and re-initialises itself automatically.

I'm at work now so I can't post a patch but the object's helpfile should explain everything you need to know. Basically, every time you make a change you need to save the patch and send the "init" message to the [js] object.

dhjdhjdhj's icon

If I understand you correctly, then every time I want to create a set of inlets and outlets, I have to paste in your object into the patcher?

II'm curious as to why this can't be done the same way as the Max Toolbox which, when loaded seems to automagically add a bunch of new commands that just work everywhere.

Luke Hall's icon

Well at least it's working now. I'll look at it over the weekend and see if I can't work on something like the toolbox which loads the patch every time you open max and hides it so that the keycommands work automatically. Technically it can be done the same way as the toolbox but give me a little bit of time!

dhjdhjdhj's icon

Hey, take all the time you need....I for one am absolutely thrilled that you have taken the time to add this very valuable enhancement to Max.

dhjdhjdhj's icon

Finally had a chance to play with this properly ----- successfully created a few instruments I wanted REALLY QUICKLY so this process really helps productivity. It's particularly handy when you are building a larger project and you're continually adding new abstractions.

It really is necessary for the inlets/outlets to be able to pick up the port comments (and probably the port hints) and inject those into the inlets/outlets, that would be tremendously helpful otherwise one still has to edit every single inlet to set the comment that will be seen by the parent using the newly saved abstraction.

Of course, now that you've done this, I can supply you with a list of suggested enhancements (grin)

The biggest one would be the ability to do for an abstraction what Encapsulate does to create subpatcher. In other words, if one selects some subset of objects that have connections, rather than replacing the selection with a subpatcher, I'd like to be able to replace with an abstraction (and retain the existing connections to the outside). Obviously, when you trigger such an operation, you'd be prompted for a filename into which to save the new abstraction.

With that feature on top of the stuff you've already done, it now becomes really feasible to do very rapid prototyping while retaining reusability and thereby iterate yourself to a finished product much faster.

Your existing implementation properly supports bottom-up development (i.e. build a little patcher with a bunch of features, then easily save as an abstraction to be used everywhere. The new piece lets you just use stuff as part of a completed "working" product, and then isolate pieces away to be reused.

I wonder if it would make sense to do this by leveraging the existing "Encapsulate" and then
(A) save the subpatcher to a file as a new abstraction
(B) remember the existing connections to the subpatcher
(C) replace the subpatcher with the new abstraction
(D) regenerate the connections

By the way, for whatever it's worth, I consider such functionality to be worth some dollars as such an investment is rapidly recovered in time saved (both in manual labor, i.e. the grunt work) and in some elimination of distraction so that one can stay more focused on the big picture. That's worth something!

Luke Hall's icon

I assumed that you'd be familiar with the workings of the subpatch and the object name and connection index would be enough of a hint for the inlet and outlet assistance strings. It would be possible to load in and parse the maxref.xml files for each object if you want to reproduce the comments exactly although this looked like a lot of work for not much gain.

However I can imagine that it would be quite useful to copy inlet and outlet comments if you are utilising the script to make connections to an existing abstraction. This way if you'd already set the child abstraction's inlet and outlet comments they would persist in the parent abstraction and so on up the chain. I can look in to adding this if you think it would be useful.

The next request is interesting. I can see how one might be able to grab the selected objects and recreate them in a new patcher completely inside javascript, you can even name and save this as an abstraction. However you'd need to wait for the file to be saved and then re-trigger the javascript to instantiate the new abstraction and patch in the connections. If you don't the script will try to create the new abstraction before you've chosen a location for it and you'll end up with a "no such object" error message. I don't think the write() funtion lets you specify a filepath either, just a name. Perhaps it is possible if you script in a [closebang] and somehow send this back to the original patch. I'll look in to it. If anyone has any amazing ideas about how they might go about accomplishing this then feel free to suggest them here, I think I'll need all the help I can get.

dhjdhjdhj's icon

Of course. But the goal of the process is to facilitate the creation of new abstractions, so there's not much point in creating the inlets automatically and then still have to edit all their properties manually afterwards. If the new abstraction (and there will be lots of them) are to be used, it doesn't help if the ports of the new abstractions don't have all the information. It's also the case that the users of the new abstractions will not necessarily be the same as the creator of them.
------
I assumed that you'd be familiar with the workings of the subpatch and the object name and connection index would be enough of a hint for the inlet and outlet assistance strings

;)
--------
The next request is interesting

Luke Hall's icon

I guess my point is that the attribute string for something like the [number] object isn't going to be any more useful than knowing that the connection is heading to the only inlet of the [number] object if the user is familiar with max (and by familiar I mean having read through the tutorials). That [number] could be controlling any number of processes or functions in max depending on the subpatch, and while max is clever, it isn't self aware enough to know what you're using it for.

If you're in the habit of giving the objects in your patches concise annotations then perhaps that might be a genuinely useful (and easy to implement) alternative. It would even be possible to set the inlet/outlet comment to the text of the nearest [comment] object, but it all depends on the patching habits of the user which can be fairly diverse.

Basically I think it's not the best idea to parse in another file in order to find the assistance string for every object's port in your patch. It's fairly time consuming (and here I mean processing time, not my personal coding time) and you don't gain a whole lot of additional information if you're even moderately well versed in the functions of most max objects.

However I'll give it a shot and run some speed tests and I'll let you and any other potential users decide whether it's worth utilising. I guess it doesn't hurt to build in the functionality and have it optional via javascript attributes.

dhjdhjdhj's icon

I'm a little confused again, I'm not sure if we're talking about the same thing.

Typically, if I have created an abstraction with some inlets and outlets, I will have already assigned useful comments to those inlets and outlets so that someone mousing over the ports when they use the abstraction, they'll see the comments.

So, for example, I have an abstraction called [GenericVST]. It has 9 input ports and two output ports. For example, some of the input ports are for MIDI notes, Aftertouch, Pitchbend and so forth (see the attached picture). Now, in a patcher, I can instantiate that abstraction by writing something like

[GenericVST "Kontakt 5" Strings]

and I get the appropriate instantiated VST.

Now, what I want to do is add inlets and outlets automatically to that instantiated VST and save the patcher as a new abstraction, simply called Strings. The idea here is that in another patcher, I can just create an object called
[Strings]

But the inlets for that new [Strings] object were connected to the ports that were in the original [GenericVST] and so I would like the Inlet in Strings that is connected to the port with the comment "Pitchbend" in the GenericVST to itself contain the comment "Pitchbend".

So when you create an inlet automatically for a port of the selected object, the comment that was associated with that port should be copied to the comment for the new inlet.

Does that make sense?

As for the speed, I'm not sure how much of a concern that is...this is design time stuff so it doesn't matter if it takes a few extra seconds.

2911.screenshot892.jpg
jpg
dhjdhjdhj's icon

Hmmm, looks like the screenshot didn't actually make it. I don't know why it won't upload

2912.screenshot892.jpg
jpg