Dictionaries - Structured Data
Max 6 introduces the concept of dictionaries, that is structured data which can be passed between objects.
In Max we create patchers, which are collections of objects that act on data sent to their inlets. Typically this data is simply a number or a symbol. Sometimes the data is slight more complex: a list combines numbers and symbols into a collection that you can pass to an object’s inlet. There is no inherent structure to a list; lists are organized simply as one item after the other.
A dictionary is a new data type based on collections of key-value pairs. Using a dictionary of terms for a spoken language as an analogy, the keys are the terms and the definitions or descriptions for those terms are the value. Each key in the dictionary is a unique symbol. Each value may be a number, symbol, or list.
Consider an example where you wish to pass a subpatcher a bundle of information to setup an ADSR envelope. Using a list, you could do it like this:
20 40 -6 150 exp no
The data in this example will make it through, but the reading of this list is a little cryptic because the interpretation of this data requires prior knowledge. A dictionary, on the other hand, structures this data by tagging the values with keys that identify each piece of information:
attack: 20 ms
decay: 40 ms
sustain: -6 db
release: 150 ms
This dictionary is made up of 6 key-value pairs. In a list format it may be onerous to also tag the values with their units, but in the dictionary it is quite natural to do and so they have been added.
The most important thing that Dictionary objects communicate to each other is a name, referring to a dictionary (a place in memory where data is stored). Dictionary objects output a message that only other Dictionary objects understand. Unsurprisingly, that message is the word "dictionary" followed by a space and the name of a dictionary where data is stored. This message is communicated from one Dictionary object to another through a patch cord in the normal Max manner.
If you are familiar with Jitter, this is the same way that Jitter works to pass around matrices.
The receiving Dictionary object receives the message in its inlet (most commonly the left inlet), gets the data from the specified place in memory, modifies the data in some way, and sends the name of the modified data out its left outlet to all connected Dictionary objects. In this way, tasks are performed by each object without necessarily knowing what the other objects are doing, and each object gets the data it needs by looking at the appropriate place in memory. Most Dictionary objects don't really do anything until they get a "dictionary" message from another Dictionary object, telling them to look at that dictionary and do something with the data there.
In many cases a Dictionary object will generate a unique name for its dictionary on its own. In other cases, it is possible (and even desirable) to tell an object what name to use for a dictionary. By explicitly naming a dictionary, we can cause objects to use that same memory space.
Caveats and Subtleties
Although passing dictionaries by reference instead of by value permits us to create system that is simpler and faster than passing by value, there are some side-effects and implications of which you should be aware. This is particularly true when there is a bifurcation in the patcher’s structure, which is to say that patch cords are fanning out from an outlet of an object to multiple objects or multiple inlets.
If you need to pass by value, then you can explicitly control this by ‘cloning’ a dictionary. To clone a dictionary, simply pass a dictionary into a ‘dict’ object as in the following example.
It is sometimes useful to represent the contents of a dictionary as a string of text.
Dictionary syntax is a list of atoms representing N key-value pairs. Keys and values are separated by a colon. If there is no value specified for a key, then a default value of "*" is used (indicating a wildcard for objects that use dictionary values for matching or validation). As an example, a dictionary of three pigs mapped to the kinds of houses they built can be represented in dictionary syntax like this:
pig1 : straw pig2 : sticks pig3 : bricks
One case where this is handy is for sending the contents of a dictionary over a network using objects such as udpsend. To "serialize" a dictionary into such a textual representation, the dict.serialize object fits the bill. The dict.deserialize object does the opposite: it converts a string of text into a named dictionary.
Dictionary Syntax is also used to specify dictionary contents as arguments to objects. Two examples are dict.pack and dict.route. The dict.pack object uses a dictionary specified as arguments to configure its inlets, which are mapped to keys that you provide, and the default values for those keys. The dict.route object uses a dictionary specified as arguments as a schema (or model) dictionary against which to validate incoming dictionaries.
Analogous to the dict object in the graphical patcher environment, the
object in JS creates a dictionary and associates it with a name. If the name is already associated with an existing dictionary, then this Dict object references that dictionary.
The following code creates two dictionaries. The first dictionary refers to the dictionary named 'ark'. The second dictionary had no name specified, so it will generate a unique symbol to use as a name. You can find out what name it was assigned by accessing the name attribute.
var d1 = new Dict("ark")
var d2 = new Dict()
var name = d2.name
post("The second Dict is named", name)
The Dict object defines the following methods.
getnames() : returns a list of all dictionary names currently in use in the environment
getkeys() : returns a list of all keys in the dictionary
getsize("foo") : returns the number of values associated with the key named "foo"
gettype("foo") : returns the type of the values associated with the key named "foo"
get("foo") : returns the value associated with the key named "foo"
set("foo", "bar") : for the key "foo", set the value to "bar"
push_to_coll("a_coll") : export the contents of the dictionary to a coll object named "a_coll"
pull_from_coll("another_coll") : import the contents of a coll object named "another_coll" into this dictionary
export_json("filename.json") : export the contents of the dictionary to a file in the JSON format
import_json("filename.json") : import a JSON file named "filename.json" into this dictionary
The Dict object defines the following attributes.
name : the name associated with this dictionary
Writing Dictionary Objects in C
A complete API for objects passing dictionaries is available to C developers in the Max Software Development Kit.