dynamically creating an deleting max objects


    May 01 2020 | 11:41 am
    Hi,
    my third question here and maybe someone can answer in contrast ;)
    I saw a post where somebody talked about creating and deleting bpatchers automatically. I wonder how this works and wether this works for any type of object.
    Can somebody tell me the class name used to create objects dynamicaly?
    i'm working with vst~ object and it seems there is no way to unload a plugin but i wouldn't need this if i can create a vst~ object dynamically.... maybe even as much vst~ objects as i give as a parameter? that would be really great.

    • May 01 2020 | 12:10 pm
      Have a look at thispatcher's help file. You can also use Javascript if you're familiar with it.
    • May 01 2020 | 12:21 pm
      since max version 4.5 it works with any object ("script newdefault"), before it only worked with GUI objects. [list script new SCRIPTINGNAME bpatcher 40 40 100 100 0 0 FILENAME 0]
    • May 01 2020 | 3:33 pm
      Ok... i need to check this. but good to know it's possible.
    • May 08 2020 | 11:34 pm
      hi,
      coming back to this one i had a look at the newdefault sample script in the thispatcher help....
      But i would also need to create patch chords and connect the objects i create dynamcially.... Is this possible too with java? It would be like a codegenerator for max devices... I guess it is not possible - so it seems it isn't with the max sdk neither. Hum...
    • May 09 2020 | 7:23 am
      no idea if it is documented anywhere, but you can also make connections with thispatcher scripting (or is javascript obligatory for your patch?) to make it work, the non-dynamic objects where you want to connect from and to also need scripting names. [list script connect OBJECT_1NAME 0 BPATCHERNAME 0] [list script connect BPATCHERNAME 0 OBJECT_2NAME 0] [list script connectcolor BPATCHERNAME 0 OBJECT_3NAME 0 13] using "disconnect" should not be required when you delete an object.
    • May 12 2020 | 4:41 am
      oh really? this is great!
      Is this also possible from the c sdk? Or do i have to write it in javascript and then somehow pass parameters to the javascript from the max external`?
    • May 12 2020 | 12:06 pm
      i have no clue what the sdk or javascript would have to do with that. using thispatcher is just like talking to the max runtime, which does the rest. :) it can, first of all, help to automate building patches, but it even works in standalones when you really need it. #P toggle 477 136 36 0; #P window setfont "Sans Serif" 9.; #P window linecount 1; #P newex 477 202 209 9109513 if $i1==1 then connect else disconnect; #P newex 381 236 491 9109513 pak script xxx OBJECT_1NAME 0 OBJECT_2NAME 0; #P button 136 453 35 0; #P newex 218 489 491 9109513 list script disconnect OBJECT_1NAME 0 OBJECT_3NAME 0; #P comment 215 213 100 9109513 OBJECT_3NAME; #P comment 68 213 100 9109513 OBJECT_2NAME; #P button 136 408 35 0; #P button 136 363 35 0; #P button 136 317 35 0; #P newex 217 444 491 9109513 list script connect OBJECT_1NAME 0 OBJECT_3NAME 0 10; #P newex 218 399 491 9109513 list script disconnect OBJECT_1NAME 0 OBJECT_2NAME 0; #P newex 218 353 491 9109513 list script connect OBJECT_1NAME 0 OBJECT_2NAME 0; #P number 235 185 44 9 0 0 0 139 0 0 0 221 221 221 222 222 222 0 0 0; #P objectname OBJECT_3NAME; #P number 100 185 44 9 0 0 0 139 0 0 0 221 221 221 222 222 222 0 0 0; #P objectname OBJECT_2NAME; #P number 172 100 44 9 0 0 0 139 0 0 0 221 221 221 222 222 222 0 0 0; #P objectname OBJECT_1NAME; #N thispatcher; #Q end; #P newobj 218 541 88 9109513 thispatcher; #P comment 148 69 100 9109513 OBJECT_1NAME; #P connect 10 0 7 0; #P connect 8 0 5 0; #P connect 9 0 6 0; #P connect 14 0 13 0; #P connect 15 0 1 0; #P connect 7 0 1 0; #P connect 13 0 1 0; #P connect 6 0 1 0; #P connect 5 0 1 0; #P connect 17 0 16 0; #P connect 16 0 15 1; #P window clipboard copycount 18;#P toggle 477 136 36 0; #P window setfont "Sans Serif" 9.; #P window linecount 1; #P newex 477 202 209 9109513 if $i1==1 then connect else disconnect; #P newex 381 236 491 9109513 pak script xxx OBJECT_1NAME 0 OBJECT_2NAME 0; #P button 136 453 35 0; #P newex 218 489 491 9109513 list script disconnect OBJECT_1NAME 0 OBJECT_3NAME 0; #P comment 215 213 100 9109513 OBJECT_3NAME; #P comment 68 213 100 9109513 OBJECT_2NAME; #P button 136 408 35 0; #P button 136 363 35 0; #P button 136 317 35 0; #P newex 217 444 491 9109513 list script connect OBJECT_1NAME 0 OBJECT_3NAME 0 10; #P newex 218 399 491 9109513 list script disconnect OBJECT_1NAME 0 OBJECT_2NAME 0; #P newex 218 353 491 9109513 list script connect OBJECT_1NAME 0 OBJECT_2NAME 0; #P number 235 185 44 9 0 0 0 139 0 0 0 221 221 221 222 222 222 0 0 0; #P objectname OBJECT_3NAME; #P number 100 185 44 9 0 0 0 139 0 0 0 221 221 221 222 222 222 0 0 0; #P objectname OBJECT_2NAME; #P number 172 100 44 9 0 0 0 139 0 0 0 221 221 221 222 222 222 0 0 0; #P objectname OBJECT_1NAME; #N thispatcher; #Q end; #P newobj 218 541 88 9109513 thispatcher; #P comment 148 69 100 9109513 OBJECT_1NAME; #P connect 10 0 7 0; #P connect 8 0 5 0; #P connect 9 0 6 0; #P connect 14 0 13 0; #P connect 15 0 1 0; #P connect 7 0 1 0; #P connect 13 0 1 0; #P connect 6 0 1 0; #P connect 5 0 1 0; #P connect 17 0 16 0; #P connect 16 0 15 1; #P window clipboard copycount 18;
    • May 12 2020 | 12:13 pm
      btw, doing that for vst~ might lead to trails in RAM or even crash your patch if you have bad luck. best practice is to turn the plug-in off ("enable 0") before deleting the object. but it can still be very clever, because this way you could for example also change the number of input and outputs and things like that, which are never possible by sending messages to objects but only by recreating them using scripting.
    • May 12 2020 | 12:41 pm
      well, i want to create the objects dynamically using javascript or better c code to control the creation of the objects. something like javascript:
      // pseudo code: Add 10 matrix~ objects and connect them. function createPatch(x) { for (i=0 ; i <10; ++i) { m = thispatcher.addObject("jit.matrix~"); m.inlets[0].connect(x); } }
      what kind if syntax is this above? seems to be not a patcher nor java script (?) where to paste it?
      yes. i also managed the vst~ object is unstable, especially unloading often crashes. i will try to "enable 0". with trails you mean memory leaks?
      ps: here is a brief description of what i' doing: http://karlchenscoderdasein.blogspot.com/2020/05/a-dynamic-signal-flow-graph.html
    • May 12 2020 | 2:19 pm
      ok i made some tests... that looks good. just one more question: Is there somewhere a complete reference of all javascript functions and objects? I mean the method "defaultnew" is not defined in "thispatcher" object. How can i know this function exists?
      thanks a lot for your help, @roman thilenius,
    • May 12 2020 | 2:50 pm
      if you are good in javascript it will probably be more flexible for mass production compared thispatcher. otoh it will perform about 50 times slower. there is a vignette which mentions both. the thispatcher section only goes to the reference. https://docs.cycling74.com/max8/vignettes/scripting_topic https://docs.cycling74.com/max8/refpages/thispatcher it is more or less limited to create/delete, show/hide, mouse/nomouse, move/offset, connect/disonnect and sending messages to objects.
    • May 12 2020 | 3:51 pm
      ok... it's getting closer....
      in the big list you gave above you have defined class names and the parameters belonging to the creation args.
      regarding:
      #P comment 215 213 100 9109513 OBJECT_3NAME;
      Question1: From where did you get the info that for example param #6 for comment is the object name? regarding #P number 172 100 44 9 0 0 0 139 0 0 0 221 221 221 222 222 222 0 0 0;
      Question2: Why does the number class not provide an object name? Is it not possible to set the object name for number?! (!!)
      Question3 objectname is the scripting name, right?
      and yes, it will be a mass production. because i wanna reflect all objects together with their creation arguments in c# types. but for this i would need the meta data about which class provides which parameters in which order. and - important - the data type of the parameter. otherwise it'll be not so compfortable as i want it to be.
      the best way would be if the cycling guys would make the object registry api public but they didn't.... will be hard to achieve...
    • May 12 2020 | 4:43 pm
      I posted somethin like this in April on a related topic, maybe you find it useful.
    • May 12 2020 | 6:40 pm
      thx that helped.... i didn't put the "script" prefix before the message.
    • May 12 2020 | 8:39 pm
      Q1: right, that is the one thing which is not full documented anywhere. when i need to know how to re-create something, i usually export a max patch as text from max version 4.x - because the old max text format is what thispatcher-scripting is based on. but it is easy, it follows a simple scheme. see my first post: script new SCRIPTINGNAME bpatcher 40 40 100 100 0 0 FILENAME 0 scripting name, name of the object, left top bottom right, and in this case two flags (0 or 1, i think for "border" and visibility), and them further info the bpatcher object requires: name of the patcher to load (not sure about the 0 now.) after that and optional are the up to 9 arguments to the object in question. for most objects it is just scripting name, objectname, position. Q2: however, the code in Q1 is not what you think, and in Q2, well that [number] has no scripting name simply because it is not a scripted object. i thought by now you figured that you have to copy and paste that code into a patcher window! :P then you will understand. Q3: yes, that is a formal variable i was using to make the text readable. you can use what you want, but it must be a unique name. if you always use the same, you can continue creating stuff, but you can no longer delete or disconnect, because the name of the last obejct will go boom if you use it for another object. there is a compromise, and that is the automatic scriptingname-numbering shown in the [thispatcher] reference website. (but i dont like it. it is better to do that on your own.)
    • May 23 2020 | 5:49 pm
      ok... now for this issue.
      Question: is it also possible to delete a object?
    • May 23 2020 | 5:57 pm
      ok i found the chapter "Scripting the patcher" in the max api documentation. i gonna use this... :)
    • May 23 2020 | 6:23 pm
      Hi Charly,
      I am hoping to clarify a few misconceptions: 1) The object registry API is public. 2) You can dynamically create/destroy any object in Max both from scripting and from C code. 3) You can programmatically connect any two objects in a patch both from scripting and C code. It is also possible to target specific inlets/outlets. 4) You should really be coding in C/C++. Patching is never gonna be stable/flexible/efficient enough for your project.
      Cheers - Luigi
    • May 23 2020 | 9:20 pm
      Yeah, what Luigi said! I wish I had gotten on to the C coding earlier, it makes Max make so much more sense.
      Charly, this may or may not be useful to you, but if you're looking for ways to build parts of the app in something more high level than C, you could always do what I'm doing in my Scheme For Max project and embed the S7 scheme interpreter in a C external to allow a mix of dynamic high level programming and low level C. It works really well, and the foreign function interface for S7 is dead easy, so making C functions that you can call from Scheme, and calling Scheme from C are both simple. S7 is similar to Guile, but has a more permissive license and is designed around the needs of computer music. Common Music is built with S7 embedded in a Juce C++ app for example, as is the Snd sound editor. And both S7 and Scheme for Max are open source and BSD licensed, so you can use anything in there that is helpful to you.
      S7's page is here: https://ccrma.stanford.edu/software/snd/snd/s7.html Scheme for Max is here: https://github.com/iainctduncan/scheme-for-max
      HTH!
    • May 23 2020 | 11:31 pm
      "4) You should really be coding in C/C++. Patching is never gonna be stable/flexible/efficient enough for your project." wouldnt you end up recreating [thispatcher] when you create and delete objects from a C++ external - and how is that the opposite of "scripting"?
    • May 24 2020 | 12:00 am
      Well, I think Luigi has a good point that managing thispatcher stuff is a lot nicer in code (at least to my tastes!) Given what I think I've read of Charly's project, he's already doing C, so I would agree that that is going to be nicer to handle in an external. But YMMV as they say!
    • May 24 2020 | 12:00 am
      There are good examples of thispatcher scripting in the SDK example code too.
    • May 24 2020 | 1:57 am
      when i need to know how to re-create something, i usually export a max patch as text from max version 4.x - because the old max text format is what thispatcher-scripting is based on.
      if I never had Max 4.x... is there any way to retrieve this?
      wouldnt you end up recreating [thispatcher] when you create and delete objects from a C++ external - and how is that the opposite of "scripting"?
      I was asking myself the same thing. I must have not quite understood what Luigi meant with:
      4) You should really be coding in C/C++. Patching is never gonna be stable/flexible/efficient enough for your project.
      Speaking of efficiency, Roman at some point said:
      if you are good in javascript it will probably be more flexible for mass production compared thispatcher. otoh it will perform about 50 times slower.
      Why exactly is that, and how does the other C and C++ approaches then compare to thispatcher? Thank you for this thread.
    • May 24 2020 | 2:31 am
      1. eventually you can also make it up from looking at the json. old type text is only better because you can (almost) copy and paste. ;) scripting an objectbox does not differ sooo much from the gui object example in the thispatcher helpfile. i´ve never tried this, but even "thing @attributes" or "poly~ foo up args 1 2 3" should be just typed in after the object name. arguments to bpatcher (see above) is a special case.
    • May 24 2020 | 2:53 am
      number and flonum are a bit weird. but in many cases the order of all these wonderful encoded numbers is represented in the objects inspector (top to bottom) #P toggle 40 237 15 0; #P window setfont "Sans Serif" 9.; #P window linecount 1; #P newex 40 174 569 9109513 cycle 50; #P newex 40 135 50 9109513 cycle; #P flonum 40 94 35 9 0 0 0 139 0 0 0 255 227 23 222 222 222 0 0 0; #P objectname flonum-name; #P flonum 40 55 35 9 0 0 0 139 0 0 0 221 221 221 222 222 222 0 0 0; #P window clipboard copycount 5;
      thispatcher scripting vs text format
      thispatcher scripting vs text format
    • May 24 2020 | 4:09 am
      well, okay, actually everything is a bit weird. for externals there are also data contained in the patcher file which are not required/possible to contain in the script, because the runtime handles that stuff exclusively. but in theory you can make yourself an automatic script generator which creates the script you need to recreate an existing object. in max 4 you would have to select the object in question and one more object in order to be be able to copy and paste a "partial" patcher file containing the object. then you have its name and its current position in the window (and possibly arguments, colors and so on.) in max 7 there are far more things an object can have (styles), so it will require a bit another approach. however, "max 4 scripting" will of course also work in v7/v8. only the position is mandatory. :)
    • May 24 2020 | 4:53 am
      object with size and argument
      #P newex 200 100 50 9109513 cycle 5; "script newdefault NAME 200 100 50 cycle 5"
      with "hide on lock" and "ignore click"
      #P hidden newex 200 100 50 9109513 cycle 5; #P noclick;
      "script newdefault NAME 200 100 50 cycle 5, script hide NAME, script ignoreclick NAME" or "script hidden newdefault NAME 200 100 50 cycle 5, script ignoreclick NAME" not sure about color, it was different in max 4 and stopped working.
    • May 24 2020 | 10:02 am
      nice discussion here.
      two points: Luigi, thanks for your clariiciation i see that all now. Just one note about the objectregistry: when i said object registry i actually meant the class registry. This means i would need to request the meta info about which classes exist and with which attributes and methods the classes have. This info i would need because i thought of writting a code generator which creates wrappers for all the objects. Is this info also accessable?
      This brings me to my second point about the high level language iain was talking about: I created a c# wrapper for max this means, most of my code is written in c# using a slim adapter layer written in c. That is very compfortable to code and the .net api is quite big too.
      i'm currently about to extend the wrapper for the "object scripting api" creating and connecting an object works like:
      var aMainIn = this.ParentPatcher.GetObject("MainIn"); var aMcAddSig = this.ParentPather.AddObject("mc.+~", "obj1"); aMainIn.Outlets[0].ConnectTo(aMcAddSig.Inlets[0]); //... aMcAddSig.Delete();
      Where "this" is the custom max external.
      Like this i can delete the old objects and create new when the graph definition of my tool changes.
      Currently there is only a "Patcher" class and a "PatcherObject" class but i would like to generate also a class for mc.+~ and all the others having a specific number of attributes like color etc. So that it's not necessarry to do it like
      aObj.SetAttribute("bgcolor", new double[]{1,0,0,1});
      with a code generator it could look like: aObj.Bgcolor = Color.FromArgb(1, 1,0,0);
      that would be much nicer...
    • May 24 2020 | 11:36 am
      First of all, let me say how much I am enjoying this thread and exchanging ideas with all of you. Lots of experienced and knowledgeable people here. Thanks for your contribution.
      Now, let's get down to business...
      two points: Luigi, thanks for your clarificiation i see that all now. Just one note about the objectregistry: when i said object registry i actually meant the class registry. This means i would need to request the meta info about which classes exist and with which attributes and methods the classes have. This info i would need because i thought of writting a code generator which creates wrappers for all the objects. Is this info also accessable?
      yes, this information is accessible. Both object registry and class registry are entirely accessible. However there are some more considerations you need to think about. For example you need to choose whether you want to know which classes exist relative to the patcher or to the whole Max environment... ...or if you are interested in classes that are used internally by Max or only in those classes that can be instantiated in a patcher (what we refer to Max externals), and more... In any case functions that may be useful for what you are trying to achieve are: class_namespace_getclassnames() object_register_getnames() object_attr_getnames() For methods is a little more involved, but when you get there, feel free to email me off-list (luigi.p.castelli [at] gmail [dot] com) and I will be happy to share some code with you.
    • May 24 2020 | 12:25 pm
      "4) You should really be coding in C/C++. Patching is never gonna be stable/flexible/efficient enough for your project." wouldnt you end up recreating [thispatcher] when you create and delete objects from a C++ external
      No, when you create/delete objects in the patcher the environment is already set up for you. There is so much more going on under the hood than just calling a destructor. In the patcher lots of choices have been already made. For example you can witness some of this when you open a patcher as text and see attributes such as "id", "maxclass", "outlettype", etc... You are in a graphical environment where you can create and delete objects in any order at any time. Objects might be communicating with each other and if you decide to suddenly delete an object in the chain, the whole ecosystem keeps working. The overall threading model has already been established for you. This is just to say that the patcher is a pretty high level environment already and if you decide to work in the patcher you are bound to follow certain rules that you can just ignore if you were coding in C/C++.
      When you are coding in C/C++ pretty much everything is open. This makes it a lot more complex, because almost everything needs to be managed, however in exchange it gives the user more freedom to make it optimal for the kind of goal he/she wants to achieve.
      and how is that the opposite of "scripting"?
      I don't think patching can be defined as the "opposite" of scripting. Probably under the hood patching and scripting use the same code.
    • May 24 2020 | 4:04 pm
      i think you can understand "patching" as using the max gui and connecting manually by hand. "scripting" can be understood as using a script language to automatically create and connect max object.
      commonly the difference between a script language and a compiler langauge is that script languages are linked at runtime where in compiled languages at least a portion of the symbols is linked at compile-time. (c# can do both using the "dynamic" keyword to enable the runtime-linking.)
      var obj = new MyClass(); obj.mymethd(); // compiletime linked dynamic dyn = obj; dyn.mymethod(); // runtime linked
      so c is not a script language but in the documentation there is a chapter "scripting the patcher" which actually means "using the c api to manipulate patcher content". also java script can be used to do this.
      i guess this is enough term-chaos to produce some misunderstandings when talking about scripting. ;-)
      @luigi: it is great that the class registry is public accessable. i will check this at some point. when i will generate wrappers i will generate for all classes. the generated classes will be included to my "ClrAdapter.dll" which has no dependency to the specific ClrMaxExternal. It can be done to generate the code on demanand and using dynamic-keywoard but this is not my intention: I wanna have all the attributes and messages compiled into a class so that the user can take advantage of intellisense and the stuff is compile-time linked.
      I just finished creating the hardwired graph. I just need to change the channels-patcher to use this generated patcher then i will see how much performance it still takes (we had another discussion about optimizing in this thread:) https://cycling74.com/forums/max-device-terribly-slow-inside-live/replies/1#reply-5ec99da667eff66332eee053
    • May 24 2020 | 4:13 pm
      so this is the method to generate the hardwired routing: (just in case someone's interested in reading some code)
      private void UpdateMixerMatrixPatcher()
      {
        var aParentPatcher = this.ParentPatcher;
        var aMixerMatrixPatcher = aParentPatcher
          .GetSubPatcher("MixerMatrixPatcher");
        var aObjPrefix = "MixerMatrixObj";
        var aGetDynObjName = new Func<string, int, string>
          (delegate (string aPrefix, int aNamedObjIdx)
        {
          return aPrefix + "[" + aNamedObjIdx + "]";
        });
        var aGetStaticObjName = new Func<string, int, string>
          (delegate (string aPrefix, int aNamedObjIdx)
        {
          return aGetDynObjName(aPrefix, aNamedObjIdx);
        });
        { // DeleteOldObjects
          var aDone = false;
          for (var aObjIdx = 0; !aDone;++aObjIdx)
          {
              var aName = aGetDynObjName(aObjPrefix, aObjIdx);
              if (aMixerMatrixPatcher.GetContainsObject(aName))
              {
                var aObj = aMixerMatrixPatcher.GetBox(aName);
                aObj.Delete();
              }
              else
              {
                aDone = true;
              }
          }
        }
      
        { // AddNewObjects
          var aObjIdx = 0;
          var aGenObjectName = new Func<string>(() =>
          {
              var aName = aGetDynObjName(aObjPrefix, aObjIdx);
              ++aObjIdx;
              return aName;
          });
          var aCsState = this.CsState;
          var aFlowMatrix = aCsState.FlowMatrix;
          var aChannels = aFlowMatrix.Channels;
          var aIoCount = this.IoCount;
          var aMainIn = aMixerMatrixPatcher.GetBox("MainIn");
          var aMainOut = aMixerMatrixPatcher.GetBox("MainOut");
          var aOutputs = new CPatBox[aIoCount];
          for(var aOuputIdx = 0; aOuputIdx < aIoCount; ++aOuputIdx)
          {
              var aVstOutName = aGetStaticObjName("VstOut", aOuputIdx);
              var aVstOutObj = aMixerMatrixPatcher.GetBox(aVstOutName);
              aOutputs[aOuputIdx] = aVstOutObj;
          }
          var aInputs = new CPatBox[aIoCount];
          for (var aInputIdx = 0; aInputIdx < aIoCount; ++aInputIdx)
          {
              var aVstInName = aGetStaticObjName("VstIn", aInputIdx);
              var aVstInObj = aMixerMatrixPatcher.GetBox(aVstInName);
              aInputs[aInputIdx] = aVstInObj;
          }
          var aAddDelay = new Func<int, int, CPatBox>(delegate
            (int aDelayChannelIdx, int aDelayTime)
          {
              var aInput = aInputs[aDelayChannelIdx]; 
              var aSkipDelayIsAllowed = false; // can not delete cable 0->0 atm...
              if (aDelayTime == 0
              && aSkipDelayIsAllowed)
              {                  
                return aInput;
              }
              else
              {
                var aArgs = "" + aDelayTime + " " + aDelayTime;
                var aObjectName = aGenObjectName();
                var aBoxText = "mc.delay~ " + aDelayTime + " " + aDelayTime;
                var aDelay = aMixerMatrixPatcher.Add(aBoxText, aObjectName);
                aDelay.Outlets[0].ConnectTo(aInput.Inlets[0]);
                return aDelay;
              }
          });
      
          for(var aChannelIdx = 0; aChannelIdx < aIoCount; ++aChannelIdx)
          {
              var aChannel = aChannels.Channels[aChannelIdx];
              var aInputObj = aChannelIdx == 0
                            ? aMainIn
                            : aOutputs[aChannelIdx];
              foreach (var aOutputChannel in aChannel.OutputsWithoutMainOut)
              {
                var aInputChannelIdx = aOutputChannel.Inputs.IndexOf(aChannel);
                var aLatencyCompensation = aOutputChannel
                  .LatencyCompensations[aInputChannelIdx];
                var aTargetInput = aAddDelay
                  (aOutputChannel.IoIdx, aLatencyCompensation);
                aInputObj.Outlets[0].ConnectTo(aTargetInput.Inlets[0]);
              }
          }
        }
      }
    • May 24 2020 | 6:26 pm
      "When you are coding in C/C++ pretty much everything is open." i still dont get it. when you coding an external in C++ you have exactly the same possibilties as the developer of the vanilla externals, and when you run that external in max, then this custom external has the same possibilties than every other external, isnt it? for example you can not output a stream of 2/3 of the runtimes´ audio signal rate or make its object box appear in pink color by default. the only difference is that you could build an external which is different from the vanilla ones say like a router object which can be connected to other instances without patch cords and combines the functions of gate and s/r.
    • May 24 2020 | 9:13 pm
      Roman, I might have not explained myself as clearly as I intended. Every time you want to interface with the patcher you are required to follow certain rules, you have to create inlets and outlets, format your data in a certain way, etc... In C/C++ you get to decide how you communicate among instances of your classes, and how you format your data. So INTERNALLY you are able to engineer the architecture of your software at a much more detailed level. Maybe the keyword I was missing is INTERNALLY. Every time you communicate with the patcher, you gotta go back to following the same rules again. However coding in C/C++ makes it possible to do the heavy lifting INTERNALLY without having to deal with the idiosyncrasies of the patcher itself. If you think about it, that summarizes the architecture of Jitter for example. In Jitter you always need to write a Max wrapper for whatever functionality the underlining Jitter object implements. Every Jitter object is patcher agnostic. The Max wrapper makes it communicate with the patcher in the way that Max requires.
    • May 25 2020 | 6:01 am
      roman,
      i'm not sure if i get your point nor i know your experience level on procedural languages. you are talking about vanilla pd? i think it is a platform completely different from max isn't it?
      we're discussing whats the advantage of writing a max external compared to max patchers? or the difference between vanilla externals compared to max externals? is vanilla compatible with max?
      generally:
      you need the way into "normal" procedural code because procedural problems are a pure hell solving them in a systems like max patcher. (though it's possible)
      when luigi says, everything is open he means literally everything. using scripting languages or other shells often has limitations. for example there is no way direct way to call java methods from a java script. once you are at c or c++ (machine level) you can literally adapt anything that exists in the computer world in the fastest possible runtime speed. (you might even write assembler/machine code there)
      that is when you need "externals" or a just a (c) api like a lot of applcations/frameworks provide (acutally max is a framework). if a component reaches a certain level of complexity you really have easier life when you go to the procedural level.
      compare this patcher
      with these few lines of code: they do quite the same just that the patcher is still buggy after >20 hours of work where the procedure works fine after <1h of coding. und runs many times faster ;) That is because the semantics of max patcher is just not suitable for such kind of problem.
      internal IEnumerable<int[]> GetFeedbackLoops(int aColumn, int aRow) { var aFeedbackLoops = new List<int[]>(); foreach (var aCurrRow in Enumerable.Range(0, this.IoCount)) { this.GetFeedbackLoops(aColumn, aRow, aCurrRow, new List<int>(), aFeedbackLoops); } return aFeedbackLoops.ToArray(); } private void GetFeedbackLoops(int aColumn, int aRow, int aCurrRow, List<int> aPath, List<int[]> aFeedbackLoops ) { foreach (var aCurrCol in Enumerable.Range(0, this.IoCount)) { if (aCurrCol == 0) { // out[0] is main out. // => Mapping to out[0] always possible. } else if ((aRow == aCurrRow && aCurrCol == aColumn) || this[aCurrCol, aCurrRow]) { if (aPath.Contains(aCurrCol)) { aFeedbackLoops.Add(aPath.ToArray()); } else { aPath.Add(aCurrCol); this.GetFeedbackLoops(aColumn, aRow, aCurrCol, aPath, aFeedbackLoops); aPath.RemoveAt(aPath.Count - 1); } } } }
    • May 25 2020 | 7:33 am
      Charly, thank you for the example. Excuse the newb question: how did you actually tell the patcher/Max this code?
    • May 25 2020 | 8:01 am
      Yes, thank you Charly. 100% agree. Charly's example is a great explanation in and of itself. Max is more of an environment for object-oriented style of programming. Patcher semantics are just not suitable for procedural programming. Frequency analysis/resynthesis is another striking example. Even though the Cycling74 team did a remarkable job implementing frequency domain processing in Max, it still suffers from many inefficiencies and limitations. (i.e. try to change the FFT size dynamically...)
    • May 25 2020 | 2:52 pm
      "or the difference between vanilla externals compared to max externals? is vanilla compatible with max?" no, "vanilla max" as in "only with factory objects". avoiding dependencies includes avoiding custom externals. :) i understood luigi would suggest to write a custom external which can script-connect other externals in a max patcher instead of using [thispatcher], so i was wondering what could be done better if you do it this way. (and it seems to make more sense to put all the functionality in one external, like you just did.) "That is because the semantics of max patcher is just not suitable for such kind of problem" if you are a max user you might already have that patcher - or a better version of it - in your abstractions collection. and have you checked that there is not an external by someone else already which does the same? :) while it is clear that there are things which are easier in C++, you withheld the information about how many hours you already invested in learning C++ before you were able to save a few hours for that example. so it remains a solution for the small minority of users who already knew it before.
    • May 25 2020 | 4:31 pm
      njaaa... i code for about 30 years now. (started with 10 or so) max i only know for some years now though i did not do to intensively with it. but it is not like "it works better if you can do it." it's more like a screwdriver and a hammer. you can use a screwdriver for a hammering a nail but it will never work so well and efficient. you see by just looking at the code size, that it is much more to write and so it behaves in all languages. choose the right language and you can do the same stuff with less code and/or more functionality. / better overview
      @omar: just imagine these two prototypes to call the f() function from c#:
      void declspec(__dllexport) f(); // c-function [DllImport("MyDll.dll")] external void f(); // c# function void main() { f(); } // main function in c# calling c function in dll.
      there is some more stuff about unmanaged exports and member methods but thats a bit more complicated. but this is required to realize calls in both ways - from the c to c# and a call in the other direction. like ping pong game ;)
    • May 25 2020 | 4:39 pm
      i understood luigi would suggest to write a custom external which can script-connect other externals in a max patcher instead of using [thispatcher], so i was wondering what could be done better if you do it this way.
      ok... well since there seems to be no difference between a "normal" max object and a custom external - each object basically can call each other objects. even without knowing it's specific type...
    • Jun 04 2020 | 5:43 pm
      so, now i forgot this one. "you can use a screwdriver for a hammering a nail but it will never work so well and efficient." it is clear that you will always code more effectice in C++ even if you are on the same level in both worlds. i am just saying that you can do a lot more in max than putting objects randomly together until the screen is full. and i somehow suspected that this is what you were doing in your first approach - and then you compared the result to your professionally assembled, 30-years-experience C code saying "look, that´s easier". :D now look how the same thing can look in max in comparison. it is less flexible and runs slower - because it is max. and in max i would have quite some problems, if i would like to extend it from 10 to 100. but which is better to read, and which is less code? and btw.: this same max code will work with strings, numbers, arrays, audio signals and video. you also dont have to remember any variables´ names, you just connect stuff to the abstraction. and it will continue to work in max 9.5 on an ARM based apple computer.
      private void UpdateMixerMatrixPatcher()
      {
        var aParentPatcher = this.ParentPatcher;
        var aMixerMatrixPatcher = aParentPatcher
          .GetSubPatcher("MixerMatrixPatcher");
        var aObjPrefix = "MixerMatrixObj";
        var aGetDynObjName = new Func<string, int, string>
          (delegate (string aPrefix, int aNamedObjIdx)
        {
          return aPrefix + "[" + aNamedObjIdx + "]";
        });
        var aGetStaticObjName = new Func<string, int, string>
          (delegate (string aPrefix, int aNamedObjIdx)
        {
          return aGetDynObjName(aPrefix, aNamedObjIdx);
        });
        { // DeleteOldObjects
          var aDone = false;
          for (var aObjIdx = 0; !aDone;++aObjIdx)
          {
              var aName = aGetDynObjName(aObjPrefix, aObjIdx);
              if (aMixerMatrixPatcher.GetContainsObject(aName))
              {
                var aObj = aMixerMatrixPatcher.GetBox(aName);
                aObj.Delete();
              }
              else
              {
                aDone = true;
              }
          }
        }
      
        { // AddNewObjects
          var aObjIdx = 0;
          var aGenObjectName = new Func<string>(() =>
          {
              var aName = aGetDynObjName(aObjPrefix, aObjIdx);
              ++aObjIdx;
              return aName;
          });
          var aCsState = this.CsState;
          var aFlowMatrix = aCsState.FlowMatrix;
          var aChannels = aFlowMatrix.Channels;
          var aIoCount = this.IoCount;
          var aMainIn = aMixerMatrixPatcher.GetBox("MainIn");
          var aMainOut = aMixerMatrixPatcher.GetBox("MainOut");
          var aOutputs = new CPatBox[aIoCount];
          for(var aOuputIdx = 0; aOuputIdx < aIoCount; ++aOuputIdx)
          {
              var aVstOutName = aGetStaticObjName("VstOut", aOuputIdx);
              var aVstOutObj = aMixerMatrixPatcher.GetBox(aVstOutName);
              aOutputs[aOuputIdx] = aVstOutObj;
          }
          var aInputs = new CPatBox[aIoCount];
          for (var aInputIdx = 0; aInputIdx < aIoCount; ++aInputIdx)
          {
              var aVstInName = aGetStaticObjName("VstIn", aInputIdx);
              var aVstInObj = aMixerMatrixPatcher.GetBox(aVstInName);
              aInputs[aInputIdx] = aVstInObj;
          }
          var aAddDelay = new Func<int, int, CPatBox>(delegate
            (int aDelayChannelIdx, int aDelayTime)
          {
              var aInput = aInputs[aDelayChannelIdx]; 
              var aSkipDelayIsAllowed = false; // can not delete cable 0->0 atm...
              if (aDelayTime == 0
              && aSkipDelayIsAllowed)
              {                  
                return aInput;
              }
              else
              {
                var aArgs = "" + aDelayTime + " " + aDelayTime;
                var aObjectName = aGenObjectName();
                var aBoxText = "mc.delay~ " + aDelayTime + " " + aDelayTime;
                var aDelay = aMixerMatrixPatcher.Add(aBoxText, aObjectName);
                aDelay.Outlets[0].ConnectTo(aInput.Inlets[0]);
                return aDelay;
              }
          });
      
          for(var aChannelIdx = 0; aChannelIdx < aIoCount; ++aChannelIdx)
          {
              var aChannel = aChannels.Channels[aChannelIdx];
              var aInputObj = aChannelIdx == 0
                            ? aMainIn
                            : aOutputs[aChannelIdx];
              foreach (var aOutputChannel in aChannel.OutputsWithoutMainOut)
              {
                var aInputChannelIdx = aOutputChannel.Inputs.IndexOf(aChannel);
                var aLatencyCompensation = aOutputChannel
                  .LatencyCompensations[aInputChannelIdx];
                var aTargetInput = aAddDelay
                  (aOutputChannel.IoIdx, aLatencyCompensation);
                aInputObj.Outlets[0].ConnectTo(aTargetInput.Inlets[0]);
              }
          }
        }
      } [/code]
    • Jun 04 2020 | 5:49 pm
      so guess we´re finished here, but we arent in the latency thread if you want https://cycling74.com/forums/latency-of-send~-and-receive~-objects