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.
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.
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.
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.
<?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:
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.
Next, we can substitute values in the metadata. This includes the "author" and some "tags". 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>
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.
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 supports one argument, and it is not optional.
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.
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.
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.
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!