Package Authoring: Writing Documentation, Part 3


    In Part 1 of this series, we familiarized ourselves with the basics needed to create our own Packages for Max. Part 2 dug deeper into details such as the package icon, license, readme, and other facets that make a complete and polished package. We saved one topic in that last article (Part 3) to be discussed on its own: authoring reference documentation for Max.

    Types of Documentation

    Reference pages, often referred to as "refpages", are the most common type of documentation that you will want to provide with your package. Reference pages provide reference material for one other file in your package. Most commonly this will be reference documentation for an external object or an abstraction (patcher), but you can provide reference pages for any type of file.
    Guides discusses a subject that may apply to any files (or none at all). One common use of a guide is to provide an overview of a package. Examples of this use are in the Ease and Link packages.
    Tutorials lead a user through a set of steps to bring them up to speed on a subject by doing the activities you prescribe. The Bach package is an excellent example of this.
    In this tutorial, we will be focusing solely on Reference Pages. However, the very same concepts apply to the other types of documentation as well.

    Formatting

    All types of documentation are authored in XML. XML is a metalanguage that allows the text content to be structured and accessed by Max in a variety of ways.
    To edit this format, a text editor with tools for validating (sometimes called "linting") XML will be useful. Options include the Atom and Sublime text editors.

    Reference Pages

    Refpages are perhaps the most straightforward and also the most useful of the documentation types. Consider the following abstraction we've created in Max: a humble time delay for Jitter matrices.
    When a user types the name into an object box Max nicely offers up our abstraction as an auto-complete option. But the auto-complete doesn't have a description, so how are we to know what this abstraction does? Furthermore, Max is not able to offer us auto-completion help with the arguments and attributes.
    And what happens if the user can't remember the name of the abstraction? How will they know what to search for in the file browser? If the abstraction had keywords or tags associated with it then searching for it will be dramatically improved!
    Once we've written a reference page, our abstraction will gain all of this extra integration with Max in addition to having a reference page which users can consult.

    Getting Started

    Let's be honest: It is hard to remember the exact XML tags and their usage. Even after working with these files for years, I still don't remember the details exactly.
    The computer is unforgiving of typos, so the easiest way to start is to find another refpage, make a copy, rename it, and then begin editing.
    There are a variety of "styles" in which refpages are written. Some of them are even written by computer programs, which usually results in a style that is difficult for humans to read. One of my favorite places for finding clean and clear refpages is Nathan Wolek's LowkeyNW package.
    We'll copy his nw.cppan~.maxref.xml file into the docs folder of our package and then rename it my.jit.delay.maxref.xml.
    Now, open the file in a text editor such as Atom or Sublime and we can get to work.
    0. Preamble
    <?xml version="1.0" encoding="utf-8" standalone="yes"?>
    <?xml-stylesheet href="./c74ref.xsl" type="text/xsl"?>
    Every refpage will start with these two lines. If they seem like gibberish, don't worry. We never need to bother with them.
    1. Our object
    <c74object name="nw.cppan~" module="msp" category="LowkeyNW, MSP Processing">
    We need to change the name to the name of our abstraction. We can also eliminate the "module" and category completely - these are holdovers from Max 6 and earlier, and they don't play a significant role in recent versions of Max. With our edits, the line now looks like this:
    <c74object name="my.jit.delay">
    2. Description
    We can then update the digest and description to reflect our abstraction's function:
        <digest>
            Delay Jitter matrices in time
        </digest>
    ​
        <description>
            Delay Jitter matrices by a specified number of frames. 
        </description>
    The digest is a super-short description used for things such as autocompletion.
    The description is a more detailed overview of the object, abstraction, or other file.
    3. Metadata
    Next, we can substitute values in the metadata. This includes the "author" and some "tags". Are are useful when searching or browsing in the file browser.
        <!--METADATA-->
        <metadatalist>
            <metadata name="author">My awesome self</metadata>
            <metadata name="tag">Jitter</metadata>
            <metadata name="tag">Delay</metadata>
            <metadata name="tag">Matrix</metadata>
        </metadatalist>
    4. Inlets and Outlets
    The original that we copied looks like this:
        <!--INLETS-->
        <inletlist>
            <inlet id="0" type="signal">
                <digest>Audio signal: mono signal to be panned.</digest>
            </inlet>
            <inlet id="1" type="signal/float">
                <digest>Panning position between 0.0 to 1.0.</digest>
                <description>
                    Input should be limited to values between 0.0 and 1.0, 
                    with hard left equivalent to 0 and hard right equivalent to 1.
                </description>
            </inlet>
        </inletlist>
    The first line here is a comment line that - in effect - does nothing other than label this section of XML for us.
    Next, we have the "inletlist" tag which will encapsulate all of the inlets we need to document. The original file is documenting two inlets, which are numbered starting from zero. Our abstraction has only one inlet, and the type is not an audio signal but a matrix. After making our edits, this section will look like this:
        <!--INLETS-->
        <inletlist>
            <inlet id="0" type="matrix">
                <digest>matrix: input to be delayed</digest>
            </inlet>
        </inletlist>
    The same is true of the outlets section. After editing, it should look like this:
        <!--OUTLETS-->
        <outletlist>
            <outlet id="0" type="matrix">
                <digest>matrix: delayed output</digest>
            </outlet>
        </outletlist>
    5. Arguments
    Arguments are defined in the same fashion as inlets and outlets - there is a tag that encapsulates the entire list of possible arguments inside of which there are individual argument tags. The original looks like this:
        <!--ARGUMENTS-->
        <objarglist>
            <objarg name="pan-position" optional="1" type="float">
                <digest>Initial panning position, default is 0.</digest>
            </objarg>
        </objarglist>
    We see one object argument, or "objarg", for the pan-position. The argument was optional and the type was a float.
    In our abstraction, we can look at the patcherargs object and see that there are 4 default arguments provided. On closer inspection, we notice that the first outlet isn't connected to anything. Additionally there is no use of #1, #2 etc. in the abstraction. So, actually our abstraction doesn't handle arguments.
    That means we can delete the entire arguments section above completely.
    6. Messages
    The pattern should be getting familiar: we have a "methodlist" which encapsulates individual methods. In Max, a method is simply a synonym for what we think of as a message.
    Our object accepts "jit_matrix" messages and messages that set the attributes. We'll deal with the latter when we document attributes. For now, we have only one message to document -let's take a look at what it should look like:
        <!--MESSAGES-->
        <methodlist>
            <method name="jit_matrix">
                <arglist>
                    <arg name="matrix-name" optional="0" type="symbol" />
                </arglist>
                <digest>
                    Matrix to be delayed
                </digest>
                <description>
                    Matrix to be delayed.
                </description>
            </method>
        </methodlist>
    Our "methodlist" contains one method, which is named "jit_matrix". As with entries we've looked at so far, there is both a "digest" (for a short description) and a "description" (for a longer description), though both are the same in this case.
    What makes messages (methods) more complicated is that any message you send in Max may have zero or more arguments. A bang message has no arguments. A replace message to a buffer~ object has many arguments, all of which are optional. Arguments for messages, just like the arguments we've seen before, are wrapped in an "arglist" container tag.
    Our "jit_matrix" message sports one argument, and it is not optional.
    7. Attributes
    Our abstraction supports several attributes, as can be seen in the patcherargs object in the patcher.
    The refpage that we copied didn't include any attributes, so we'll need to find a suitable model elsewhere. Taking a look at the refpage from the "ease" object in the Ease package, we find this:
        <!--ATTRIBUTES-->
    ​
        <attributelist>
    ​
            <attribute name='output_range' get='1' set='1' type='float64' size='1' >
                <digest>Expected numeric range for the output</digest>
                <description>Expected numeric range for the output.</description>
            </attribute>
    ​
            <attribute name='input_range' get='1' set='1' type='float64' size='1' >
                <digest>Expected numeric range for the input</digest>
                <description>Expected numeric range for the input.</description>
            </attribute>
    ​
            <attribute name='function' get='1' set='1' type='long' size='1' >
                <digest>Easing function to be applied or generated</digest>
                <description>Easing function to be applied or generated.</description>
            </attribute>
    ​
        </attributelist>
    As expected, there is an "attributelist" encapsulating several individual attributes. Let's copy and paste that into our refpfage file, and then modify it to reflect our attributes:
        <attributelist>
    ​
            <attribute name='delayframes' get='0' set='1' type='long' size='1' >
                <digest>Number of frames to delay</digest>
                <description>Number of frames to delay relative to the input.</description>
            </attribute>
    ​
            <attribute name='adapt' get='1' set='0' type='long' size='1' >
                <digest>Automatically adjust matrix properties</digest>
                <description>
                    Automatically adjust matrix properties to match 
                    the incoming matrices' properties. 
                </description>
            </attribute>
    ​
            <attribute name='maxdelayframes' get='0' set='1' type='long' size='1' >
                <digest>Maximum number of frames that may be delayed.</digest>
                <description>
                    Maximum number of frames that may be delayed.
                    This results in more (or less) memory being set aside 
                    to store the history from the input.
                </description>
            </attribute>
    ​
        </attributelist>
    We changed the following:
    • all of the attribute names
    • all of the attribute digests
    • all of the attribute descriptions
    • all of the attribute types — previous types were float64, but these attributes are not floats, they are ints. In refpages we specify that by saying they are long.
    • all of the attributes get flags. In our abstraction we support setting the attributes, but there is no way in our patcher to get the current value of the attributes. So we set this to 0 instead of 1.
    8. See Also
    The See Also section is where we cross-link to other resources or similar objects that might be useful to the user of our abstraction.
        <!--SEEALSO-->
        <seealsolist
            <seealso name="jit.matrixset"/>
        </seealsolist>
    9. End Matter
    Remember that lonely line all the way back in step 1? That was an opening tag. We need to close it at the end, or our XML will be invalid.
    </c74object>

    Validation

    Speaking of invalid XML… invalid XML is bad. It won't work at all, and ugly error messages will get churned out to the Max window. Don't ship a package with bad XML!
    But how do you know if you've made an error?
    Every text editor is different. If you didn't catch it above, we do in fact have an error. The Atom editor has "Linter" package that will automatically and continuously check your XML as you type. Here is what it looks like.
    As you can see, we are missing the closing angle bracket on line 79 for the seealsolist. Add it, and all is well in the world again.

    Testing

    Now we can test all this out and see the fruits of our labors. Let's try autocompletion first:
    The object's description is provided - great! If we begin to type an '@' symbol, we will see attribute autocompletion as well.
    And selecting one of the options will provide the additional description to aid users of our abstraction.

    Congratulations

    One of the true keys to producing a polished and professional package is providing reference documentation. Now that you are equipped with the last piece of the puzzle, you are fully enabled to produce great Packages.
    Don't be a stranger - please share your amazing packages with the Max community. We look forward to seeing what you make!

    • Nov 16 2017 | 5:21 am
      Hi, and thank you for your tutorial. Some weeks ago I posted on the forum a question about maxref xmls... now I have a very good place to repost it ;) so:
      can someone help me to understand how to insert an image on the reference of an object? I tried some solutions but with no results..
      thank you for your help!
    • Nov 22 2017 | 4:00 pm
      An example of this is the matrixctrl refpage. It includes this line: <img src="images/image137.png" />
      Hope this helps!
    • Jan 31 2018 | 7:08 am
      Oh my, this tutorial is extremely valuable. Thank you so much. There are still a few loose ends however which I hope you can shed some light on… --> should the messages set in the xml-file also pop up as options when clicking on the arrow to the left of the object? With custom built abstractions and their reference set in the manner above this doesn't seem to work and they are greyed out. By the same token, the menu is entitled 'jpatcher' instead of the abstraction name and the only available attributes are the common box attributes and not those set in the reference file — albeit, everything shows up correctly in the custom made maxhelp-file documentation and Reference respectively. I guess that setting the information on possible messages to the object is meant to help in the documentation but not with actual patching? The attributes cannot be changed in the inspector either and hence need to be modified manually through a designated inlet I assume? Talking of which, if one could be able to change the attributes by means of messages to the first inlet (i.e. by choosing Messages>[attribute] from the left-hand menu, would the patch have to be set up accordingly, that is, routing the available attributes to their operation targets manually from the first/respective inlet? Or would they automatically be routed through the [patcherargs] object? (the latter would of course be super handy)… I hope this makes sense, and of course I hope for a (de)lightful answer. Thank you very much for your input! Kudos.
      'jpatcher' attributes only & 'Messages' greyed out
      auto-fill messages containing attributes (probably linked to the 'Message' options in the menu)
      Documentation works fine!
    • Jan 31 2018 | 3:23 pm
      Thanks, Oni! These are great ideas to improve this further. I'll start a discussion on this and see if we can do anything.
      Cheers!
    • Feb 01 2018 | 7:37 am
      Cool, thanks for the reply! Meanwhile I have been digging a little deeper and stumbled upon the following differences between my current custom package and those released, i.e. the LowkeyNW or the ease package (all listed criteria applies to the latter): • in the 'externals' folder we find, e.g. the ease.mxo file (Mac) and generally no abstractions (.maxpat) as such • the 'Messages' and 'Attributes' can be chosen from the [ease] object's menu • likewise, attributes can be set with messages and these support auto-completion of arguments • the [ease] object itself cannot be opened and/or edited • like with factory max objects, the ease.maxref.xml file states that one should not edit it as for changes will be lost — this hints at an automatically created file which will be rewritten after one changes its contents, unlike the custom maxref.xml files we learnt to create
      Upon investigation, the ease.maxref.xml does not employ any other content which doesn't match your instructions. That is to say, I couldn't find any details which pertain to a different handling of information.
      However, not being particularly apt at coding, not to mention mindful enough, I haven't a solid idea how the .mxo files are actually being created by the user and combed through the internet to no avail. I simply copied the ease.mxo file into my package under 'externals', changed it to the name of my abstraction e voilá > the 'Messages' option suddenly appeared! Though, my abstraction was no longer valid (found) within Max and thus browned out and since then I was not able to reproduce this magic.. what has changed though, is that the menu is now entitled 'jbogus' instead of 'jpatcher'……
      With my botched understanding of all this I am extremely grateful for your input because what you've explained has all worked for me. The reason I am keen to control my abstractions with messages without setting everything up manually is the amazing potential for myself and other users when available attributes and messages can be chosen from a menu and the message boxes will be auto-completed. Furthermore, it is rather appealing to manage arguments in a patcher solely through [patcherargs] (hopefully auto-linked to inlet #1??), even when set with messages..
      I reckon the crux of the matter are the .mxo files — meaning, if I could create those from the abstraction in the correct fashion (let alone know how to) then it seems all of the above would work.
      But I don't :/ So yeah, if you come up with a solution: awesome. Thanks again!
      attributes are available for [ease] as well as message boxes with auto-completion for attributes and arguments
    • Feb 16 2018 | 3:17 pm
      Hi Oni,
      The Ease package is a little special as the external objects in it do indeed generate the refpages automatically. The magic behind this is a part of a new C++ development kit for externals that we have not yet released (but I hope is coming soon).
      Hope that doesn't cause too much confusion!