CNMAT Max/MSP Summer Workshop 2012
Lab Assignment 3


July 24, 2012

Summary

In this lesson, we will create a data storage and retrieval mechanism. We’ll use the [function ] object as our model, so that we can build up envelopes. These envelopes will eventually help us to make interesting sounds, but for now let’s focus on building an interesting function storage/recall tool.

Topics

transfer functions, data storage, data recall, encapsulation, abstraction.

Objects Introduced

[function ], [textedit ], [line ], [route ], [zl ], [prepend ], [trigger ], [line~ ]

Relevant Tutorials

Basic

1.
Abstraction
2.
Data Collections
3.
Procedural Drawing (using line)

MSP

1.
Additive Synthesis (including Envelopes)

1 Data Collection, Storage, and Recall

1.1 Data Collection

1.
Open up a new patch window
2.
Place a new [function ] object into the patch (type “n” for “new object”, type in the word “function”, and hit “enter”. You should see a new box appear in your patch).
3.
Lock the patch, and try drawing some shapes by clicking in the box with your cursor. To erase the points you have made, create a
clear
message, and patch it to the inlet of the [function ] object.
4.
Place a new [trigger bang dump] object into the patch (remember that “trigger” can be abbreviated as “t”), and connect its rightmost outlet (the one associated with “dump”) to the inlet of the [function ] object.
5.
Place a new [button ] object in your patch, and connect the rightmost outlet of the [function ] object to the inlet of the [button ] object (this outlet will output a
bang
when we mouse up after a click). Then, connect the outlet of the [button ] object to the inlet of the [trigger ] object.
6.
Create a [zl 2048 group] object1 , and connect the leftmost outlet (bang) of the [trigger ] object to left inlet of the [zl 2048 group] object.
7.
Now connect the 3rd outlet of the [function ] object to the left inlet of the [zl 2048 group] object. When the [function ] object receives a
dump
message, the third outlet will output all of the points that it contains in successive lists of (x,y) pairs. We want to collect these into a single list which we can accomplish with [zl 2048 group]. Once the [function ] object is done outputting our coordinates, we will send a bang to [zl 2048 group], which will output them all into one long list.

Your patch should now look something like this:

Let’s pause for a second to review what we’ve just done:

1.2 Data Storage

Since our data is now bundled together via [zl 2048 group], we can easily send it to [dict ] to store it as the values in an index but there’s one issue: how do we create indices for these data? Let’s use a handy mode of [zl ] called “join”, which joins two lists together. We’ll join our data together with a one element list that describes the data. Something like

mydata 0.1 0.3 44.41 33.1
. This functionality is very similar to that of [pack ], [pak ], and [prepend ]. How would you achieve the same basic functionality using the prepend object instead of [zl join]?

1.
Create a [zl join] object.
2.
Connect the lefmost outlet of [zl 2048 group] to the right inlet of [zl join].

Now we need a way to get the names of our indices (preset names) into the left inlet of [zl join]. For this task, we can use a [textedit ] object.

3.
Create a new [textedit ] object.
4.
Create a new [route ] object, with the argument “text”, as in [route text].
5.
Go into the [textedit ] object’s inspector, and check the attribute labeled “Return Enters Text”.
6.
Connect the left outlet of the [textedit ] object to the left inlet of [route ].
7.
Connect the left outlet of [route text] to the left inlet of [zl join].

Each time we click, a new set of points are sent into [zl 2048 group], and then into [zl join]. After a list leaves [zl 2048 group], the object is cleared and ready for a new set of points. In this way we are building up lists to store into the right inlet of [zl join] as we edit functions, and once we have finished editing, we can enter in a descriptor for that particular [function ]. When we enter in this text, it gets output together with the larger list.

Now we just need to make sure that we give the correct storage message to dict. Our current messages would read

myPresetName 0.1 0.3 44.41 33.1
, but we really need
set myPresetName 0.1 0.3 44.41 33.1
. If you’d like to know more about guidelines for storage of data in [dict ], refer to the object’s help file.
8.
Create a new [prepend ] object, and give it an argument, so that it reads: [prepend set]. Also create a new [dict ] object.
9.
Connect the outlet of [zl join] to the inlet of the [prepend set] object.
10.
Connect the outlet of the [prepend set] object to the inlet of the [dict ] object.
11.
Create a new [zl slice 1] object. This will route off the preset name from the response dict issues from a ’get’ message (We’ll cover this later).
12.
Create a new [zl iter 2] object. This is going to allow us to break our singular list back up into xy pairs, which can then be sent back to the [function ] object to restore our saved preset.2
13.
Connect the 2nd outlet of the [dict ] object to the left inlet of the [zl slice 1] object.
14.
Connect the right outlet of the [zl slice 1 ] object (everything but the name of our preset) to the left inlet of the [zl iter 2] object.
15.
Connect the left outlet of [zl iter 2] to the inlet of the [function ] object.

Now we are ready to test our preset system.

16.
Draw a function in the [function ] object, then give it a name. Create a few more functions and give them unique names (you can clear the [function ] object by sending it the
clear
message).
17.
Double-click on the [dict ] object to see its contents. Do you see your two presets? If not, go back to the previous steps to review.

1.3 Data Recall

Stop and take a moment to make sure everything is functioning as expected. If so, let’s move on to recalling our presets.

1.
Create two new message boxes containing two of the function names you have chosen.
2.
Create a new [trigger ] object like so: [trigger s clear], and connect the outlet of the
mypreset1
message to the inlet of [trigger s clear]. Do the same for the other.
3.
Connect the right outlet of [trigger s clear] (the
clear
message) to the inlet of [function ].
4.
Connect the middle outlet of [trigger s clear] (“s” stands for “symbol” and will pass your preset name through) to the inlet of [dict ], which will call up the data for that particular preset name.
5.
Create a new [prepend ] object like so: [prepend get], and connect its outlet to the left inlet of the [dict ] object.
6.
Connect the left outlet of [trigger s clear] to the inlet of [prepend get], which will call up the data for that particular preset name.
7.
Lock the patch, and notice that when you click back and forth between presets that the [function ] object clears properly before accepting new data, and that our presets are restored properly.

Your patch should now look something like the following:

2 Encapsulation

So how can we encapsulate this? Let’s start by figuring out what portions of our patch we would like to be within a subpatcher. Here are the relevant objects:

2.1 Re-routing

One way to make the process of encapsulation easier is to program the encapsulation depth first; that is, to patch as though you’re already inside of a subpatcher. In this case, we might want a [route ] object to act as a catch-all for our various instructions and data types. As we’ll see momentarily, [route ] can handle the various types of input we’ll want all in one inlet, instead of having too many inlets to label and keep track of. Keep in mind that we’d like our module to respond to special messages like

store
,
recall
, and
bang
. Let’s also include lists. Make a new object like so: [route list store recall bang].

Let’s break the following connections:

1.
Disconnect [function ] from [zl 2048 group]
2.
Disconnect the “bang” outlet of [trigger b dump] from [zl 2048 group]
3.
Delete any
message
containing preset names (We’ll add a feature here making these obsolete).
4.
Disconnect the outlet of [route text] from [zl join]

Now add some new objects:

1.
Create a new [prepend store] object.
2.
Connect your [route text] object’s left outlet to the inlet of this new [prepend store] object.
3.
Duplicate this object chain ([textedit ], [route text], [prepend store]).
4.
Rename the [prepend store] to [prepend recall], and connect both [prepend recall] and [prepend store] to the left inlet of [route list store recall bang].
5.
Connect the left outlet of [trigger bang dump] to [route list store recall bang].
6.
Make sure that the right outlet of [trigger bang dump] is still going to the [function ] object.

Finally, we’ll make the new [route ] connections:

7.
Connect [route ]’s bang outlet to [zl 2048 group]’s left inlet
8.
Connect [route ]’s list outlet to [zl 2048 group]’s left inlet
9.
Connect [route ]’s store outlet to [zl join]’s left inlet
10.
Connect [route ]’s recall outlet to [trigger s clear]’s inlet

2.2 Patch to subpatch

Now select all relevant objects for encapsulation, like so:

Choose “Edit / Encapsulate” to encapsulate the contents:

Let’s rename this subpatcher to have a name that we’ll remember. In our case, we should name it “env_store_recall”, as we’ll be using this particular filename later:

3 Abstraction

Take a minute to make sure that everything is working with your new subpatcher. If you are satisfied, move on to making this handy utility an abstraction so that we can use many of them easily:

1.
Double-click on [patcher env_store_recall].
2.
Give [dict ] a changeable argument via the # sign, like so: [dict #1]
3.
Choose “File / Save-as...”, and save the file to disk inside of your max enabled folder (Notice that the filename is drawn from the edited subpatcher name).
4.
To test whether or not the file is now seen by Max, name a new object and type “env_store_recall” as the name:
5.
Try giving your abstraction a unique name, such as “myenvs” or similar, like so: [env_store_recall myenvs]. Notice that you can have many abstractions with different names. These abstractions will refer to different envelope data sets.

If you’d like, feel free to copy the “cnmat_function_manager.maxhelp” file and replace the abstraction’s name there with our lesson’s name (“env_store_recall”). Now you’re ready to create, manage, and use many envelopes. Think about how this lesson might relate to other objects, in terms of managing their state.