Max API Development Guide
Max API Development Guide
8.2.0
Cycling ’74
Chapter 1
Objects in C: A Roadmap
Max has an extensive API for developing new objects in C.Before you start learning about it, however, we would like to
save you time and make sure you learn the minimum about the API for what you need to do. Therefore, we've made a
brief list of application areas for object development along with the sections of this document with which you'll probably
want to become familiar.
For logic and arithmetic objects, such as new mathematical functions or more complex conditional operations than
what is offered in Max, it should be sufficient to read the Anatomy of a Max Object section.
For objects that use Data Structures, you'll want to read, in addition, the Atoms and Messages section to learn about
Max's basic mechanisms for representing and communicating data.
If you are interested in writing interfaces to operating system services, you may need to learn about Max's Threading
model and The Scheduler.
For objects that deal with time and timing you'll want to learn about The Scheduler. If you're interested in tempo-based
scheduling, you'll want to read the section on ITM and look at the delay2 example.
To create new user interface gadgets, you'll want to read all of the above, plus the section on Attributes and the
Anatomy of a UI Object. The section on JGraphics will also be helpful.
To create objects with editing windows, things are much more complicated than they used to be. You'll need to learn
everything about UI objects, plus understand the scripto example object project.
For patcher scripting and interrogation objects, the section on Scripting the Patcher, plus a few of the examples will be
very helpful. It is also helpful to have a clear conceptual understanding of the patcher, which might be aided by reading
the patcher scripting sections of the js object documentation.
Max 6 introduced support for passing structured data with the Dictionary Passing API.
Cycling ’74
2 Objects in C: A Roadmap
To create audio filters and signal generators, read the Anatomy of a Max Object, then read the Anatomy of a MSP Object
section. MSP objects make use of Creating and Using Proxies when receiving multiple audio inputs, so familiarity with
that concept could be helpful.
For audio objects that output events (messages), you'll need to use the services of The Scheduler, so we suggest
reading about that.
For UI objects for analyzing and controlling audio, you'll need to learn about regular MSP objects as well as Max UI
objects.
Information on updating MSP objects from Max 5 or earlier for 64-bit audio (introduced in Max 6) is located in
Appendix: Updating Externals for Max 6.
Max 8 introduced MC for working with multi-voice/multi-channel signals. More information is available in the MC chapter.
The Jitter Object Model outlines some important basic information about Jitter's flexible object model. Jitter Max Wrappers
describes how to write Max wrapper objects that contain Jitter objects for use in the Max patcher world.
Matrix Operator QuickStart and Matrix Operator Details describe how to create a particular type of Jitter object called
matrix operators, or MOPs. OB3D QuickStart and OB3D Details describe how to create OB3D Jitter objects for use
in rendering OpenGL scenes. Scheduler and Low Priority Queue Issues covers important threading and timing issues
when building Jitter objects. Jitter Object Registration and Notification explains how Jitter objects can be registered by
name and notify clients as they change or important events occur. Using Jitter Objects in C provides some examples of
how to instantiate and take advantage of Jitter objects from C, just as one would from Java, Javascript, or the patcher.
Finally, The JXF File Specification and Jitter Networking Specification contain information relating to the data formats
involved in the JXF file format and Jitter networking protocols, respectively.
Cycling ’74
Chapter 2
2.1 Building
This SDK documentation is accompanied by a series of projects for compiling some example Max external objects. The
details of how to build these projects are documented below in separate sections for the Mac and Windows.
When you build the example projects, the resulting Max external will be located in a folder called "externals" at the top
level of the SDK package. This is located a couple of folders up from the project and source file.
Place the SDK package in your "Packages" folder in order to load and test the externals you build in Max itself.
We have provided a basic script that will build all of the projects in the SDK at once. This script is written using the
Ruby language. A Ruby interpretter is standard on the Mac. On windows you can download and install Ruby (1.9.3
recommended) from http://rubyinstaller.org/ .
Run from Terminal.app (Mac) or a Command Prompt (Windows) by cd'ing into the examples directory, and then
running:
ruby build.rb
2.3 Mac
Max external objects for the Mac are Mach-O bundles (folders that appear to be files) whose filenames must end with
the .mxo extension. The example projects have been tested using Xcode 6.2. Xcode is available through the Apple Mac
Store.
After installing Xcode, if you wish to run the aforementioned Ruby script, you will also need to install the Command Line
Tools. This is done via the menu item: Xcode > Open Developer Tool > More Development Tools...
Cycling ’74
4 Development System Information
The example projects are set up to have Development and Deployment build configurations. The Development configu-
ration does not optimize and ensures debugging symbols are present. The Deployment configuration creates a universal
binary and performs optimization.
The files required for the projects are included in the project folders with the exception of the following two files:
• Info.plist
• maxmspsdk.xcconfig
These two files are located one folder-level up from the project folder, and are required for the Xcode project to build the
Max external.
External objects use dynamic linking to access the API functions provided by the Max application. When an object is
loaded, calls to functions inside the application are resolved by the operating system to the correct memory address.
Due to the fact that "Max" could exist as an application, a standalone you create, or a library inside another application,
an object's Xcode project does not link directly to a framework, library, or application. Instead, the list of permitted
symbols is provided to the linker. This list is defined in the aforementioned maxmspsdk.xcconfig file.
Audio objects will link against MaxAudioAPI.framework and Jitter objects link against JitterAPI.framework. Alternatively,
you could also provide linker flags as we have provided for Max itself. The most recent version of all frameworks will
be found inside the application you are using (they are found inside the application bundle in Contents/Frameworks).
In addition, there are versions inside the c74support folder provided with the SDK. These will be used only to link your
objects; they are never actually executed.
Xcode uses something called the Frameworks Search Path to locate frameworks when linking. The example SDK
projects use a frameworks search path with a c74support folder two levels up from your the folder containing your
Xcode project. If you rearrange the SDK folders, projects may not find the frameworks and will fail to link properly.
Furthermore, even though we specify the frameworks search path, Xcode seems to look in /Library/Frameworks first. If
you have installed a version of the Max SDK for version 4.6 or ealier, you may have older versions of MaxAPI.framework
and MaxAudioAPI.framework in /Library/Frameworks. When you try to link objects that contain references to functions
only defined in the newest MaxAPI.framework, the link may fail because the projects are using the old frameworks. To
fix this, you'll need to remove the Max frameworks from /Library/Frameworks. If you want to develop objects for both the
Max 4.6 and Max 5 SDKs on the same machine, you'll need to modify your 4.6 projects to specify a Frameworks Search
Path, and relocate the 4.6 frameworks to the specified location.
2.4 Windows
Max external objects for Windows are Dynamic Link Libraries (DLLs) whose filenames must end with the .mxe extension
(for 32-bit builds) or .mxe64 (for 64-bit builds). These DLLs will export a single function called "ext_main" which is called
by max when the external object is first loaded. Generally these DLLs will import functions of the Max API from the
import library "MaxAPI.lib" which is located in the c74support\max-includes\ folder. External objects that use audio
functionality will import functions from the import library "MaxAudio.lib" which is located in c74support\msp-includes\.
External objects that use Jitter functionality will import functions from the import library "jitlib.lib" which is located in
c74support\jit-includes\.
Cycling ’74
2.5 Important Project Settings 5
The example projects are in Visual C++ 2013 format (vcxproj files). A free version of Visual C++ can be obtained from
Microsoft at http://www.microsoft.com/express/. You will want to choose "Visual Studio Express 2013
for Windows Desktop".
The projects are set up to have both a Debug and a Release configuration. The Release configuration is optimized
whereas the Debug one is not. Note that for debugging purposes you can exercise your object in the Max Runtime since
the copy protection for the Max Application will interfere when run under the debugger.
Another thing to note is that Max has a private build of the Microsoft C Runtime Library for historical and backward
compatibility reasons. It is recommended that you link with Microsoft's standard C runtime library rather than the Max
C runtime library. When you include "ext.h" from the max API it will include ext_prefix.h which for the release build
will automatically cause your project to use the max C runtime library. To use the Microsoft C Runtime define the C
preprocessor macro MAXAPI_USE_MSCRT before including ext.h.
The easiest way to create a new external is to choose one of the existing SDK examples, duplicate it, and then change
only the settings that need to be changes (such as the name of the project). This will help to guarantee that important
project settings are correct. Project settings of particular importance are noted below.
2.5.1 Mac
Particularly important for Max externals on the Mac are that the Info.plist is correct set up and that the "Force Package
Info Generation" is set to true. Without these your object may fail to load on some machines.
2.5.2 Windows
In the preprocessor definitions for the Visual Studio project it is important to define WIN_VERSION and EXT_WIN_←-
VERSION to ensure that the headers are set up properly.
2.6 Platform-specificity
If you are writing a cross-platform object and you need to do something that is specific to one platform, the Max API
headers provide some predefined symbols you can use.
#ifdef MAC_VERSION
// do something specific to the Mac
#endif
#ifdef WIN_VERSION
// do something specific to Windows
#endif
Another reason for conditional compilation is to handle endianness on the Mac platform. If you are still supporting
PowerPC, you may have situations where the ordering of bytes within a 16- or 32-bit word is important. ext_byteorder.h
provides cross-platform tools for manipulating memory in an endian-independent way.
2.7 Configuration
As the Max API evolves, the use of a number of older legacy functions are discouraged. Use of said functions will issue
a 'deprecation' warning when you try to compile the code. To disable these deprecation warnings you can define the
preprocessor symbol C74_NO_DEPRECATION in the target preprocessor section of your IDE.
Cycling ’74
6 Development System Information
Cycling ’74
Chapter 3
Max objects are written in the C language, and the Max API is C-based.
You could use C++ but we don't support it at the API level. Writing a Max object in C, you have five basic tasks:
4) writing a new instance routine that creates a new instance of the class, when someone makes one or types its name
into an object box
5) writing methods (or message handlers) that implement the behavior of the object
Let's look at each of these in more detail. It's useful to open the simplemax example project as we will be citing examples
from it.
Most of the basic Max API is included in the files ext.h and ext_obex.h. These are essentially required for any object.
Beyond this there are specific include files for more specialized objects.
#include "ext.h" // should always be first, followed by ext_obex.h and any other files.
Cycling ’74
8 Anatomy of a Max Object
Basic Max objects are declared as C structures. The first element of the structure is a t_object, followed by whatever
you want. The example below has one long structure member.
typedef struct _simp
{
t_object s_obj; // t_object header
long s_value; // something else
} t_simp;
Your structure declaration will be used in the prototypes to functions you declare, so you'll need to place above these
prototypes.
The initialization routine, which must be called ext_main, is called when Max loads your object for the first time. In the
initialization routine, you define one or more classes. Defining a class consists of the following:
1) telling Max about the size of your object's structure and how to create and destroy an instance 2) defining methods
that implement the object's behavior 3) in some cases, defining attributes that describe the object's data 4) registering
the class in a name space
In order for Max to call the ext_main() function on your compiled external, that function must be "exported" or made
public. This is accomplished by using the C74_EXPORT macro in the prototype of the ext_main() function, which is
provided for you automatically in the "ext.h" header file.
class_new() creates a class with the new instance routine (see below), a free function (in this case there isn't one, so
we pass NULL), the size of the structure, a no-longer used argument, and then a description of the arguments you type
when creating an instance (in this case, there are no arguments, so we pass 0).
class_addmethod() binds a C function to a text symbol. The two methods defined here are int and bang.
class_register() adds this class to the CLASS_BOX name space, meaning that it will be searched when a user tries to
type it into a box.
Finally, we assign the class we've created to a global variable so we can use it when creating new instances.
More complex classes will declare more methods. In many cases, you'll declare methods to implement certain API
features. This is particularly true for UI objects.
Cycling ’74
3.4 New Instance Routine 9
The standard new instance routine allocates the memory to create an instance of your class and then initializes this
instance. It then returns a pointer to the newly created object.
The first line uses the global variable s_simp_class we defined in the initialization routine to create a new instance of the
class. Essentially, the instance is a block of memory of the size defined by the class, along with a pointer to the class
that permits us to dispatch messages correctly.
The next line initializes our data. More complex objects will do a lot more here, such as creating inlets and outlets. By
default, the object being created will appear with one inlet and no outlets.
Finally, in the last line, we return a pointer to the newly created instance.
We are now ready to define some actual behavior for our object by writing C functions that will be called when our
object is sent messages. For this simple example, we will write only two functions. simp_int will be called when our
object receives numbers. It will store the received number in the s_value field. simp_bang will be called when our object
receives a bang. It will print the value in the Max window. So, yes, this object is pretty useless!
The C functions you write will be declared according to the arguments the message requires. All functions are passed
a pointer to your object as the first argument. For a function handling the int message, a single second argument that is
a long is passed. For a function handling the bang message, no additional arguments are passed.
This simply copies the value of the argument to the internal storage within the instance.
The post() function is similar to printf(), but puts the text in the Max window. post() is very helpful for debugging,
particularly when you cannot stop user interaction or real-time computation to look at something in a debugger.
You can also add a float message, which is invoked when a floating-point number is sent to your object. Add the following
to your initialization routine:
class_addmethod(c, (method)simp_float, "float", A_FLOAT, 0);
Then write the method that receives the floating-point value as follows:
void simp_float(t_simp *x, double f)
{
post("got a float and it is %.2f", f);
}
Cycling ’74
10 Anatomy of a Max Object
Cycling ’74
Chapter 4
You are familiar with inlets and outlets when connecting two objects together in a patcher.
To receive data in your object or send data to other objects, you need to create the C versions of inlets and outlets. In
this section, we'll explain what inlets and outlets are, how to create them, and how to use them. We'll also discuss a
more advanced type of inlet called a proxy that permits a message to be received in any of your object's inlets. Proxies
are used by audio objects to permit inlets to handle both signals and normal Max messages.
By default, every object shows one inlet. Additional inlets appear to the right of the default inlet, with the rightmost inlet
being created last.
Inlets are essentially message translators. For example, if you create an int inlet, your object will receive the "in1"
message instead of the "int" message when a number arrives at this newly created inlet. You can use the different
message name to define special behavior for numbers arriving at each inlet. For example, a basic arithmetic object in
Max such as + stores the number to be added when it arrives in the right inlet, but performs the computation and outputs
the result when a number arrives in the left inlet.
Outlets define connections between objects and are used to send messages from your object to the objects to which it
is connected. What is not obvious about an outlet, however, is that when you send a number out an outlet, the outlet-
sending function does not return until all computation "below" the outlet has completed. This stack-based execution
model is best illustrated by observing a patch with the Max debugger window. To understand this stack-based model it
may be helpful to use the breakpoint and debugging features in Max and follow the stack display as you step through
the execution of a patch. Outlets, like inlets, appear in the order you create them from right-to-left. In other words, the
first inlet or outlet you create will be the visually farthest to the right.
Proper use of an inlet involves two steps: first, add a method that will respond to the message sent via the inlet in your
initialization routine, and second, create the inlet in your new instance routine. (Creating inlets at any other time is not
supported.)
There are three types of inlets: int, float, and custom. We'll only describe int and float inlets here because proxies are
generally a better way to create an inlet that can respond to any message. For int inlets, you'll bind a function to a
message "in1", "in2", "in3" etc. depending on the inlet number you assign. Here's how to create a single inlet using
"in1"...
Cycling ’74
12 Inlets and Outlets
In your new instance routine, after calling object_alloc() to create your instance:
intin(x, 1);
The method that will be called when an int is received in the right inlet:
void myobject_in1(t_myobject *x, long n)
{
// do something with n
}
Creating a single inlet in this way gives your object two inlets (remember that it always has one by default). If you want
to create multiple inlets, you'll need to create them in order from right to left, as shown below:
intin(x, 2); // creates an inlet (the right inlet) that will send your object the "in2" message
intin(x, 1); // creates an inlet (the middle inlet) that will send your object the "in1" message
Inlets that send float messages to your object are created with floatin() and translate the float message into "ft1","ft2","ft3"
etc. Example:
In initialization routine:
class_addmethod(c, (method)myobject_ft1, "ft1", A_FLOAT, 0);
Method:
void myobject_ft1(t_myobject *x, double f)
{
post("float %.2f received in right inlet,f);
}
Note that you can mix int and float inlets, but each inlet must have a unique number. Example:
intin(x, 2);
floatin(x, 1);
You create outlets in your new instance routine. Outlet creators return a pointer that you should store for later use when
you want to send a message. As with inlets, outlets are created from right to left.
Here's a simple example. First we'll add two void pointers to our data structure to store the outlets for each instance.
typedef struct _myobject
{
t_object m_ob;
void *m_outlet1;
void *m_outlet2;
} t_myobject;
These outlets are type-specific, meaning that we will always send the same type of message through them. If you want
to create outlets that can send any message, use outlet_new(). Type-specific outlets execute faster, because they make
a direct connection to the method handler that will be called at the time you send a message. When we want to send
messages out these outlets, say, in our bang method, we do the following:
void myobject_bang(t_myobject *x)
{
outlet_bang(x->m_outlet2);
outlet_int(x->m_outlet1, 74);
}
The bang method above sends the bang message out the m_outlet2 outlet first, then sends the number 74 out the
m_outlet1. This is consistent with the general design in Max to send values out outlets from right to left. However, there
is nothing enforcing this design, and you could reverse the statements if you felt like it.
A more general message-sending routine, outlet_anything(), will be shown in the Atoms and Messages section.
Cycling ’74
4.3 Creating and Using Proxies 13
A proxy is a small object that controls an inlet, but does not translate the message it receives. Instead it sets a location
inside your object's data structure to a value you associate with the inlet. If the message comes "directly" to your object
via the left inlet, the value will be 0. However, in order to be thread-safe, you should not read the value of this "inlet
number" directly. Instead, you'll use the proxy_getinlet() routine to determine the inlet that has received the message.
The advantage of proxies over regular inlets is that your object can respond to any message in all of its inlets, not just
the left inlet. As a Max user, you may already appreciate the proxy feature without knowing it. For example, the pack
object can combine ints, floats, lists, or symbols arriving in any of its inlets. It uses proxies to make this happen. MSP
audio objects that accept signals in more than one inlet use proxies as well. In fact, the proxy capability is built into the
way you create audio objects, as will be discussed in the Anatomy of a MSP Object section.
If your object's non-left inlets will only respond to ints or floats, implementing proxies is usually overkill.
4.4 Example
First, add a place in your object to store the proxy value. You shouldn't access this directly, but the proxy needs it.
Second, you'll need to store the proxy, because you need to free it when your object goes away. If you create many
proxies, you'll need to store pointers to all of them, but all proxies share the same long integer value field.
typedef struct _myobject
{
t_object m_obj;
long m_in; // space for the inlet number used by all the proxies
void *m_proxy;
} t_myobject;
In your new instance routine, create the proxy, passing your object, a non-zero code value associated with the proxy,
and a pointer to your object's inlet number location.
x->m_proxy = proxy_new((t_object *)x, 1, &x->m_in);
If you want to create regular inlets for your object, you can do so. Proxies and regular inlets can be mixed, although
such a design might confuse a user of your object.
Finally, here is a method that takes a different action depending on the value of x->m_in that we check using
proxy_getinlet().
void myobject_bang(t_myobject *x)
{
switch (proxy_getinlet((t_object *)x)) {
case 0:
post("bang received in left inlet");
break;
case 1:
post("bang received in right inlet");
break;
}
}
Cycling ’74
14 Inlets and Outlets
Cycling ’74
Chapter 5
When a Max object receives a message, it uses its class to look up the message selector ("int", "bang", "set" etc.) and
invoke the associated C function (method).
This association is what you are creating when you use class_addmethod() in the initialization routine. If the lookup fails,
you'll see an "object doesn't understand message" error in the Max window.
Message selectors are not character strings, but a special data structure called a symbol (t_symbol). A symbol holds a
string and a value, but what is more important is that every symbol in Max is unique. This permits you to compare two
symbols for equivalence by comparing pointers, rather than having to compare each character in two strings.
The "data" or argument part of a message, if it exists, is transmitted in the form of an array of atoms (t_atom). The atom
is a structure that can hold integers, floats, symbols, or even pointers to other objects, identified by a tag. You'll use
symbols and atoms both in sending messages and receiving them.
To illustrate the use of symbols and atoms, here is how you would send a message out an outlet. Let's say we want to
send the message "green 43 crazy 8.34." This message consists of a selector "green" plus an array of three atoms.
First, we'll need to create a generic outlet with outlet_new in our new instance routine.
x->m_outlet = outlet_new((t_object *)x, NULL);
The second argument being NULL indicates that the outlet can be used to send any message. If the second argument
had been a character string such as "int" or "set" only that specific message could be sent out the outlet. You'd be
correct if you wondered whether intout() is actually just outlet_new(x, "int").
Now that we have our generic outlet, we'll call outlet_anything() on it in a method. The first step, however, is to assemble
our message, with a selector "green" plus an array of atoms. Assigning ints and floats to an atom is relatively simple,
but to assign a symbol, we need to transform a character string into a symbol using gensym(). The gensym() function
returns a pointer to a symbol that is guaranteed to be unique for the string you supply. This means the string is compared
with other symbols to ensure its uniqueness. If it already exists, gensym() will supply a pointer to the symbol. Otherwise
it will create a new one and store it in a table so it can be found the next time someone asks for it.
void myobject_bang(t_object *x)
{
t_atom argv[3];
atom_setlong(argv, 43);
atom_setsym(argv + 1, gensym("crazy"));
atom_setfloat(argv + 2, 8.34);
outlet_anything(x->m_outlet, gensym("green"), 3, argv);
}
Cycling ’74
16 Atoms and Messages
In the call to outlet_anything() above, gensym("green") represents the message selector. The outlet_anything() function
will try to find a message "green" in each of the objects connected to the outlet. If outlet_anything() finds such a
message, it will execute it, passing it the array of atoms it received.
If it cannot find a match for the symbol green, it does one more thing, which allows objects to handle messages gener-
ically. Your object can define a special method bound to the symbol "anything" that will be invoked if no other match is
found for a selector. We'll discuss the anything method in a moment, but first, we need to return to class_addmethod()
and explain the final arguments it accepts.
To access atoms, you can use the functions atom_setlong(), atom_getlong() etc. or you can access the t_atom structure
directly. We recommend using the accessor functions, as they lead to both cleaner code and will permit your source to
work without modifications when changes to the t_atom structure occur over time.
In the simp example, you saw the int method defined as follows:
class_addmethod(c, (method)simp_int, "int", A_LONG, 0);
The A_LONG, 0 arguments to class_addmethod() specify the type of arguments expected by the C function you have
written. A_LONG means that the C function accepts a long integer argument. The 0 terminates the argument specifier
list, so for the int message, there is a single long integer argument.
The other options are A_FLOAT for doubles, A_SYM for symbols, and A_GIMME, which passes the raw list of atoms
that were originally used to send the Max message in the first place. These argument type specifiers define what are
known as "typed" methods in Max. Typed methods are those where Max checks the type of each atom in a message to
ensure it is consistent with what the receiving object has said it expects for a given selector.
If the atoms cannot be coerced into the format of the argument type specifier, a bad arguments error is printed in the
Max window.
There is a limit to the number of specifiers you can use, and in general, multiple A_FLOAT specifiers should be avoided
due to the historically unpredictable nature of compiler implementations when passing floating-point values on the stack.
Use A_GIMME for more than four arguments or with multiple floating-point arguments.
You can also specify that missing arguments to a message be filled in with default values before your C function receives
them. A_DEFLONG will put a 0 in place of a missing long argument, A_DEFFLOAT will put 0.0 in place of a missing
float argument, and A_DEFSYM will put the empty symbol (equal to gensym("")) in place of a missing symbol argument.
The symbol argument s is the message selector. Ordinarily this might seem redundant, but it is useful for the "anything"
method as we'll discuss below.
argc is the number of atoms in the argv array. It could be 0 if the message was sent without arguments. argv is the array
of atoms holding the arguments.
Cycling ’74
5.3 Writing "Anything" Methods 17
For typed messages, the atoms will be of type A_SYM, A_FLOAT, or A_LONG. Here is an example of a method that
merely prints all of the arguments.
void myobject_printargs(t_myobject *x, t_symbol *s, long argc, t_atom *argv)
{
long i;
t_atom *ap;
post("message selector is %s",s->s_name);
post("there are %ld arguments",argc);
// increment ap each time to get to the next atom
for (i = 0, ap = argv; i < argc; i++, ap++) {
switch (atom_gettype(ap)) {
case A_LONG:
post("%ld: %ld",i+1,atom_getlong(ap));
break;
case A_FLOAT:
post("%ld: %.2f",i+1,atom_getfloat(ap));
break;
case A_SYM:
post("%ld: %s",i+1, atom_getsym(ap)->s_name);
break;
default:
post("%ld: unknown atom type (%ld)", i+1, atom_gettype(ap));
break;
}
}
}
You can interpret the arguments in whatever manner you wish. You cannot, however, modify the arguments as they may
be about to be passed to another object.
As previously mentioned, your object can define a special method bound to the symbol "anything" that will be invoked if
no other match is found for a selector. For example:
class_addmethod(c, (method)myobject_anything, "anything", A_GIMME, 0);
Your function definition for an anything method follows the same pattern as for all other A_GIMME methods:
void myobject_anything(t_myobject *x, t_symbol *s, long argc, t_atom *argv)
{
object_post( (t_object*)x,
"This method was invoked by sending the ’%s’ message to this object.",
s->s_name);
// argc and argv are the arguments, as described in above.
}
Cycling ’74
18 Atoms and Messages
Cycling ’74
Chapter 6
The Scheduler
It keeps track of time in double-precision, but the resolution of the scheduler depends on the user's environment pref-
erences. The scheduler also works in conjunction with a low-priority queue, which permits time-consuming operations
that might be initiated inside the scheduler to be executed in a way that does not disrupt timing accuracy.
Most objects interface with the scheduler via a clock (#t_clock) object. A clock is associated with a task function that will
execute when the scheduler's current time reaches the clock's time. There is also a function called schedule() that can
be used for one-off delayed execution of a function. It creates a clock to do its job however, so if your object is going to
be using the scheduler repeatedly, it is more efficient to store references to the clocks it creates so the clocks can be
reused.
The scheduler is periodically polled to see if it needs to execute clock tasks. There are numerous preferences Max
users can set to determine when and how often this polling occurs. Briefly:
• The Overdrive setting determines whether scheduler polling occurs in a high-prority timer thread or the main
thread
• The Interval setting determines the number of milliseconds elapse between polling the scheduler
• The Throttle setting determines how many tasks can be executed in any particular scheduler poll
Similar Throttle and Interval settings exist for the low-priority queue as well.
For more information refer to the Timing documentation. While the details might be a little overwhelming on first glance,
the important point is that the exact time your scheduled task will execute is subject to variability. Max permits this level
of user control over the scheduler to balance all computational needs for a specific application.
Cycling ’74
20 The Scheduler
1. Add a member to your object's data structure to hold a pointer to the clock object
typedef struct _myobject
{
t_object m_obj;
void *m_clock;
} t_object;
2. Write a task function that will do something when the clock is executed. The function has only a single argument,
a pointer to your object. The example below gets the current scheduler time and prints it.
void myobject_task(t_myobject *x)
{
double time;
sched_getftime(&time);
post("instance %lx is executing at time %.2f", x, time);
}
1. In your new instance routine, create the clock (passing a pointer to your object and the task function) and store
the result in your object's data structure.
x->m_clock = clock_new((t_object *)x, (method)myobject_task);
2. Schedule your clock. Use clock_fdelay() to schedule the clock in terms of a delay from the current time. Below
we schedule the clock to execute 100 milliseconds from now.
clock_fdelay(x->m_clock, 100.);
If you want to cancel the execution of a clock for some reason, you can use clock_unset().
clock_unset(x->m_clock);
Note that if you call clock_delay() on a clock that is already set, its execution time will be changed. It won't execute twice.
A qelem ("queue element") is used to ensure that an operation occurs in the low-priority thread. The task function
associated with a #t_qelem is executed when the low-priority queue is serviced, always in the main (user interface)
thread. Any qelem that is "set" belongs to the low-priority queue and will be executed as soon as it serviced.
There are two principal things you want to avoid in the high priority thread: first, time-consuming or unpredictable
operations such as file access, and second, anything that will block execution for any length of time – for example,
showing a dialog box (including a file dialog).
The procedure for using a qelem is analogous to that for using a clock.
1. Add a member to your object's data structure to hold a pointer to the qelem
typedef struct _myobject
{
t_object m_obj;
void *m_qelem
} t_myobject;
Cycling ’74
6.3 Defer 21
2. Write a task function that will do something when the qelem is executed. The function has only a single argument,
a pointer to your object.
void myobject_qtask(t_myobject *x)
{
post("I am being executed a low priority!"
}
3. In your new instance routine, create the qelem (passing a pointer to your object and the task function) and store
the result in your object's data structure.
x->m_qelem = qelem_new((t_object *)x, (method)myobject_qtask);
4. Set the qelem by using qelem_set(). You could, for example, call qelem_set() in a clock task function or in direct
response to a message such as bang or int.
qelem_set(x->m_qelem);
If you want to cancel the execution of a qelem for some reason, you can use qelem_unset().
qelem_unset(x->m_qelem);
1. In your object's free routine, call qelem_free(). Do not call object_free() or freeobject() – unlike the clock, the
qelem is not an object.
qelem_free(x->m_qelem);
Note that if you call qelem_set() on a qelem that is already set, it won't execute twice. This is a feature, not a bug, as
it permits you to execute a low-priority task only as fast as the low-priority queue operates, not at the high-priority rate
that the task might be triggered. An example would be that a number box will redraw more slowly than a counter that
changes its value. This is not something you need to worry about, even if you are writing UI objects, as Max handles it
internally (using a qelem).
6.3 Defer
The defer function and its variants use a qelem to ensure that a function executes at low-priority. There are three
variants: defer(), defer_low(), and defer_medium(). The difference between using defer() and a qelem is that defer() is
a one-shot deal – it creates a qelem, sets it, and then gets rid of it when the task function has executed. The effect of
this is that if you have some rapid high-priority event that needs to trigger something to happen at low-priority, defer()
will ensure that this low-priority task happens every time the high-priority event occurs (in a 1:1 ratio), whereas using a
qelem will only run the task at a rate that corresponds to the service interval of the low-priority queue. If you repeatedly
defer() something too rapidly, the low-priority queue will become backlogged and the responsiveness of the UI will suffer.
A typical use of defer() is if your object implements a read message to ask the user for a file. Opening the dialog in the
timer thread and waiting for user input will likely crash, but even if it didn't, the scheduler would effectively stop.
To use defer(), you write a deferred task function that will execute at low priority. The function will be passed a pointer
to your object, plus a symbol and atom list modeled on the prototype for an anything method. You need not pass any
arguments to the deferred task if you don't need them, however.
void myobject_deferredtask(t_myobject *x, t_symbol *s, long argc, t_atom *argv)
{
post("I am deferred");
}
To call the task, use defer() as shown below. The first example passes no arguments. The second passes a couple of
long atoms.
defer((t_object *)x, (method)myobject_deferredtask, NULL, 0, NULL);
t_atom av[2];
atom_setlong(av, 1);
atom_setlong(av+ 2, 74);
defer((t_object *)x, (method)myobject_deferredtask, NULL, 2, av);
Defer copies any atoms you pass to newly allocated memory, which it frees when the deferred task has executed.
Cycling ’74
22 The Scheduler
defer()
If executing at high priority, defer() puts the deferred task at the front of the low-priority queue. If not executing at
highpriority, defer() calls the deferred task immediately.
defer_low()
At all priority levels, defer_low() puts the deferred task at the back of the low-priority queue.
defer_medium()
If executing at high priority, defer_medium() puts the deferred task at the back of the low-priority queue. If not executing
at high priority, defer_medium() calls the deferred task immediately.
6.4 Schedule
The schedule() function is to clocks as defer() is to qelems. Schedule creates a clock for a task function you specify and
calls clock_fdelay() on it to make the task execute at a desired time. As with defer(), schedule() can copy arguments to
be delivered to the task when it executes.
A schedule() variant, schedule_defer(), executes the task function at low priority after a specified delay.
Cycling ’74
Chapter 7
Memory Allocation
There are two types of calls, those for pointers and those for handles. Handles are pointers to pointers, and were used
in the early Mac OS to permit memory to be relocated without changing a reference, and many Mac OS API calls used
handle. There are a few legacy Max API calls that use handles as well, but in general, unless the OS or Max requires
the use of a handle, you're probably better off using the simpler pointer.
Longtime Max object programmers may have used memory calls getbytes() and freebytes() in the past, but all memory
calls now use same underlying OS mechanisms, so while getbytes() and freebytes() are still supported, they are re-
stricted to 32K of memory or less due to the arguments they use, and we recommend the use of sysmem_newptr() and
sysmem_freeptr() instead.
Here are some examples of allocating and freeing pointers and handles.
char *ptr;
char **hand;
ptr = sysmem_newptr(2000);
post("I have a pointer %lx and it is %ld bytes in size",ptr, sysmem_ptrsize(ptr));
ptr = sysmem_resizeptrclear(ptr, 3000);
post("Now I have a pointer %lx and it is %ld bytes in size",ptr, sysmem_ptrsize(ptr));
sysmem_freeptr(ptr);
hand = sysmem_newhandle(2000);
post("I have a handle %lx and it is %ld bytes in size",hand, sysmem_handlesize(hand));
sysmem_resizehandle(hand, 3000);
post("Now the handle %lx is %ld bytes in size",hand, sysmem_ptrsize(hand));
sysmem_freehandle(hand);
Cycling ’74
24 Memory Allocation
Cycling ’74
Chapter 8
An MSP object that handles audio signals is a regular Max object with a few extras.
Refer to the simplemsp∼ example project source as we detail these additions. simplemsp∼ is simply an object that
adds a number to a signal, identical in function to the regular MSP +∼ object if you were to give it an argument of 1.
When creating the class with class_new(), you must have a free function. If you have nothing special to do, use
dsp_free(), which is defined for this purpose. If you write your own free function, the first thing it should do is call
dsp_free(). This is essential to avoid crashes when freeing your object when audio processing is turned on.
c = class_new("mydspobject", (method)mydspobject_new, (method)dsp_free, sizeof(t_mydspobject), NULL, 0);
After creating your class with class_new(), you must call class_dspinit(), which will add some standard method handlers
for internal messages used by all signal objects.
class_dspinit(c);
Your signal object needs a method that is bound to the symbol "dsp" – we'll detail what this method does below, but the
following line needs to be added while initializing the class:
class_addmethod(c, (method)mydspobject_dsp64, "dsp64", A_CANT, 0);
Cycling ’74
26 Anatomy of a MSP Object
The new instance routine must call dsp_setup(), passing a pointer to the newly allocated object pointer plus a number
of signal inlets the object will have. If the object has no signal inlets, you may pass 0. The simplemsp∼ object (as an
example) has a single signal inlet:
dsp_setup((t_pxobject *)x, 1);
dsp_setup() will make the signal inlets (as proxies) so you need not make them yourself.
If your object will have audio signal outputs, they need to be created in the new instance routine with outlet_new().
However, you will never access them directly, so you don't need to store pointers to them as you do with regular outlets.
Here is an example of creating two signal outlets:
outlet_new((t_object *)x, "signal");
outlet_new((t_object *)x, "signal");
The dsp64 method specifies the signal processing function your object defines along with its arguments. Your object's
dsp64 method will be called when the MSP signal compiler is building a sequence of operations (known as the DSP
Chain) that will be performed on each set of audio samples. The operation sequence consists of a pointers to functions
(called perform routines) followed by arguments to those functions.
To add an entry to the DSP chain, your dsp64 method uses the "dsp_add64" method of the DSP chain. The dsp_add64
method is passed an a pointer to your object, a pointer to a perform64 routine that calculates the samples, an optional
flag which may alter behavior, and a generic pointer which will be passed on to your perform routine.
object_method(dsp64, gensym("dsp_add64"), x, mydspobject_perform64, 0, NULL);
The perform routine is not a "method" in the traditional sense. It will be called within the callback of an audio driver,
which, unless the user is employing the Non-Real Time audio driver, will typically be in a high-priority thread. Thread
protection inside the perform routine is minimal. You can use a clock, but you cannot use qelems or outlets.
The free function for the class must either be dsp_free() or it must be written to call dsp_free() as shown in the example
below:
void mydspobject_free(t_mydspobject *x)
{
dsp_free((t_pxobject *)x);
// can do other stuff here
}
Cycling ’74
Chapter 9
Here are some techniques for implementing additional features found in most signal objects.
To implement unit generators such as filters and ramp generators, you need to save internal state between calls to your
object's perform routine. Here is a very simple low-pass filter (it just averages successive samples) that saves the value
of the last sample in a vector to be averaged with the first sample of the next vector. First we add a field to our data
structure to hold the value:
typedef struct _myfilter
{
t_pxobject f_obj;
double f_sample;
} t_myfilter;
Then, in our dsp method, we pass a pointer to the object as one of the DSP chain arguments. The dsp method also
initializes the value of the internal state, to avoid any noise when the audio starts.
void myfilter_dsp64(t_myfilter *x, t_object *dsp64, short *count, double samplerate, long maxvectorsize, long
flags)
{
object_method(dsp64, gensym("dsp_add64"), x, myfilter_perform64, 0, NULL);
x->f_sample = 0.0;
}
Here is the perform routine, which obtains the internal state before entering the processing loop, then stores the most
recent value after the loop is finished.
void myfilter_perform64(t_myfilter *x, t_object *dsp64, double **ins, long numins, double **outs, long numouts,
long sampleframes, long flags, void *userparam)
{
double *in = ins[0]; // first inlet
double *out = outs[0]; // first outlet
int n = sampleframes; // vector size
double samp = x->f_sample; // read from internal state
double val;
while (n--) {
val = *in++;
*out++ = (val + samp) * 0.5;
samp = val;
}
x->f_sample = samp; // save to internal state
}
Cycling ’74
28 Advanced Signal Object Topics
The third argument to the dsp method is an array of numbers that enumerate the number of objects connected to each
of your objects inputs and outputs. More advanced dsp methods can use this information for optimization purposes. For
example, if you find that your object has no inputs or outputs, you could avoid calling 'dsp_add64' altogether. The MSP
signal operator objects (such as +∼ and ∗∼) use this to implement a basic polymorphism: they look at the connections
count to determine whether the perform routine should use scalar or signal inputs. For example, if the right input has no
connected signals, the user can add a scalar value sent to the right inlet.
To implement this behavior, you have a few different options. The first option is to write two different perform methods,
one which handles the two-signal case, and one which handles the scalar case. The dsp method looks at the count
array and passes a different function to dsp_add64.
if (count[1]) // signal connected to second inlet
object_method(dsp64, gensym("dsp_add64"), x, mydspobject_twosigperform64, 0, NULL);
else
object_method(dsp64, gensym("dsp_add64"), x, mydspobject_scalarperform64, 0, NULL);
The second option is to store the value of the count array for a particular signal in your object's struct. Then the perform
method can make the decision whether to use the signal value or a scalar value that has been stored by the object. In
this case, many objects use a single sample value from the signal as a substitute for the scalar. Using the first sample
(i.e., the value at index 0) is a technique that works for any vector size, since vector sizes could be as small as a single
sample. Here is an example of this technique for an object that has two inputs and one output. The connection count
for the right input signal is stored in a struct member named m_count:
x->m_count = count[1];
object_method(dsp64, gensym("dsp_add64"), x, mydspobject_perform64, 0, NULL);
Here is a perform routine that uses the connection count information as passed in the format shown above:
void mydspobject_perform64(t_mydspobject *x, t_object *dsp64, double **ins, long numins, double **outs, long
numouts, long sampleframes, long flags, void *userparam)
{
t_mydspobject *x = (t_mydspobject *)w[1];
int connected = x->m_count;
double *in = ins[0];
double *in2 = ins[1];
double *out = outs[0];
int n = sampleframes;
double in2value;
// get scalar sample or use signal depending on whether signal is connected
in2value = connected ? *in2 : x->m_scalarvalue;
// do calculation here
// ...
}
To access a named buffer∼ object for either reading or writing sample values, refer to the Buffers reference.
Cycling ’74
Chapter 10
Max objects, such as the one you write, are C data structures in which methods are dynamically bound to functions.
Your object's methods are called by Max, but your object can also call methods itself. When you call a method, it is
essential to know whether the method you are calling is typed or not.
Calling a typed method requires passing arguments as an array of atoms. Calling an untyped method requires that you
know the exact arguments of the C function implementing the method. In both cases, you supply a symbol that names
the method.
In the typed method case, Max will take the array of atoms and pass the arguments to the object according to the
method's argument type specifier list. For example, if the method is declared to have an argument type specifier list of
A_LONG, 0, the first atom in the array you pass will be converted to an int and passed to the function on the stack. If
there are no arguments supplied, invoking a typed method that has A_LONG, 0 as an argument type specifier will fail.
To make typed method calls, use object_method_typed() or typedmess().
In the untyped method case, Max merely does a lookup of the symbol in the object, and, if a matching function is found,
calls the function with the arguments you pass.
Certain methods you write for your object, such as the assist method for describing your object and the DSP method
in audio objects, are declared as untyped using the A_CANT argument type specifier. This means that Max will not
typecheck the arguments you pass to these methods, but, most importantly, a user cannot hook up a message box to
your object and send it a message to invoke an untyped method. (Try this for yourself – send the assist message to a
standard Max object.)
When you use an outlet, you're effectively making a typed method call on any objects connected to the outlet.
10.1 Attributes
Attributes are descriptions of data in your object. The standardization of these descriptions permits Max to provide a
rich interface to object data, including the pattr system, inspectors, the quick reference menu, @ arguments, etc.
It is essential that you have some understanding of attributes if you are going to write a UI object. But non-UI objects
can make use of attributes as well. The discussion below is not specific to UI objects. It does however, use the recently
introduced system of macros in ext_obex_util.h (included in ext_obex.h) for defining attributes, as well as describing them
using attributes of attributes (attr attrs). You can read more detailed descriptions of the underlying attribute definition
mechanisms on a per-function basis in the Attributes reference.
Cycling ’74
30 Sending Messages, Calling Methods
While attributes can be defined for a specific instance of an object, it's much more common to define an attribute for
a class. In such a case, each instance of the class will have the attribute description, but the value will be instance
specific. The discussion here focuses only on class attributes.
When an attribute is declared and is made user-settable, a user can send a message to your object consisting of the
attribute name and arguments that represent the new value of the attribute. For example, if you declare an attribute
called trackcount, the message trackcount 20 will set it to 20. You don't need to do anything special to obtain this
behavior. In addition, user-settable attributes will appear when the user opens the inspector on your object.
If you define your attribute as an offset attribute, you describe its location (and size) within your object's C data structure.
Max can then read and write the data directly. You can also define custom getter and setter routines if the attribute's
value is more complex than simply a stored number. As a theoretical example, you could have an object with an attribute
representing the Earth's population. If this value was not able to be stored inside your object, your custom getter routine
could initiate a global census before returning the result. A custom setter for the earth's population might do something
nasty if the value was set to zero. If you are not a misanthrope, you can take advantage of the ability to set such an
attribute to be read-only.
Attributes are defined when you are defining methods in your initialization routine. You can define your attributes before
your methods if you like, but by convention, they are typically defined after the methods. For each definition, you'll
specify the name, size, and offset of the corresponding member in your object's data structure that will hold the data.
For example, let's say we have an object defined as follows:
typedef struct _myobject {
t_object m_ob;
long m_targetaddress;
t_symbol *m_shipname;
char m_compatmode;
} t_myobject;
We want to create attributes for m_targetaddress, m_shipname, and m_compatmode. For each data type (and a few
others), there are macros in ext_obex_util.h that will save a fair amount of typing. So, for example, we can define an
attribute for m_targetaddress that uses CLASS_ATTR_LONG. Here are attribute definitions for all of the members of
our data structure above.
CLASS_ATTR_LONG(c, "targetaddress", 0, t_myobject, m_targetaddress);
CLASS_ATTR_SYM(c, "shipname", 0, t_myobject, m_shipname);
CLASS_ATTR_CHAR(c, "compatibilitymode", 0, t_myobject, m_compatmode);
In some cases, it is not enough to have Max read and write data in your object directly. In some cases (as in the world
population example above) you may have data you need to calculate before it can be returned as a value. In other
cases, you may need to do something to update other object state when an attribute value changes. To handle these
challenges, you can define custom attribute getter and setter routines. The getter will be called when the value of your
attribute is accessed. The setter will be called when someone changes the value of your attribute.
As an example, suppose we have an object that holds onto an array of numbers, and we want to create an attribute for
the size of the array. Since we'll want to resize the array when the attribute value changes, we will define a custom setter
for our attribute. The default getter is adequate if we store the array size in our object, but since we want to illustrate how
to write an attribute getter, we'll write the code so that the array size is computed from the size of the memory pointer
we allocate. First, here is our object's data structure:
Cycling ’74
10.2 Receiving Notifications 31
We also have prototypes for our custom attribute setter and getter:
t_max_err myobject_size_get(t_myobject *x, t_object *attr, long *argc, t_atom **argv);
t_max_err myobject_size_set(t_myobject *x, t_object *attr, long argc, t_atom *argv);
Here is how we define our attribute using CLASS_ATTR_ACCESSORS macro to define the custom setter and getter.
Because we aren't really using an "offset" due to the custom setter and getter, we can pass any data structure member
as a dummy. (Only the default attribute getter and setter will use this offset, and they are out of the picture.)
CLASS_ATTR_LONG(c, "size", 0, t_myobject, m_ob);
CLASS_ATTR_ACCESSORS(c, "size", myobject_size_get, myobject_size_set);
Now, here is an implementation of the custom setter for the array size. For the setter, we use the handy Max API function
sysmem_resizeptr so we can effectively "resize" our array and copy the data into it in one step. The setter uses atoms,
so we have to obtain the value from the first item in the argv array.
t_max_err myobject_size_set(t_myobject *x, t_object *attr, long argc, t_atom *argv)
{
long size = atom_getlong(argv);
if (size < 0) // bad size, don’t change anything
return 0;
if (x->m_data)
x->m_data = (long *)sysmem_resizeptr((char *)x->m_data, size * sizeof(long));
else // first time alloc
x->m_data = (long *)sysmem_newptr(size * sizeof(long));
return 0;
}
The getter also uses atoms for access, but we are returning a pointer to an array of atoms. The caller of the getter has
the option to pre-allocate the memory (passing in the length in argc and the pointer to the memory in argv) or pass in
0 for argc and set the contents of argv to NULL and have the getter allocate the memory. The easiest way to handle
this case is to call the utility function atom_alloc, which will figure out what was passed in and allocate memory for a
returned atom if necessary.
t_max_err myobject_size_get(t_myobject *x, t_object *attr, long *argc, t_atom **argv)
{
char alloc;
long size = 0;
atom_alloc(argc, argv, &alloc); // allocate return atom
if (x->m_data)
size = sysmem_ptrsize((char *)x->m_data) / sizeof(long); // calculate array size based on ptr size
atom_setlong(*argv, size);
return 0;
}
As an alternative to writing a custom setter, you can take advantage of the fact that objects receive a "notify" message
whenever one of their attributes is changed. The prototype for a notify method is as follows:
t_max_err myobject_notify(t_myobject *x, t_symbol *s, t_symbol *msg, void *sender, void *data);
Add the following to your class initialization so your notification method will be called:
class_addmethod(c, (method)myobject_notify, "notify", A_CANT, 0);
The notify method can handle a variety of notifications (more documentation on this is coming soon!), but the one we're
interested in is "attr_modified" – the notification type is passed to the notify method in the msg argument. Here is an
example of a notify method that prints out the name of the attribute that has been modified. You could take any action
instead. To obtain the name, we interpret the data argument to the notify method as an attribute object. As an attribute
is a regular Max object, we can use object_method to send it a message. In the case we are sending the message
getname to the attribute object to obtain its name.
t_max_err myobject_notify(t_myobject *x, t_symbol *s, t_symbol *msg, void *sender, void *data)
Cycling ’74
32 Sending Messages, Calling Methods
{
t_symbol *attrname;
if (msg == gensym("attr_modified")) { // check notification type
attrname = (t_symbol *)object_method((t_object *)data, gensym("getname")); // ask attribute object
for name
object_post((t_object *)x, "changed attr name is %s",attrname->s_name);
}
return 0;
}
Cycling ’74
Chapter 11
Anatomy of a UI Object
Max user interface objects are more complex than normal non-user-interface objects.
If you have nothing in particular to display, or do not need to create a unique interface for user interaction or editing, it
would be better to avoid writing one. However, if you want the details, we have them for you!
In order to create a user interface object, you'll need to be familiar with Attributes, as they are used extensively. If you
examine a toggle object in the inspector in Max, you will see a few attributes that have been defined as belonging to the
toggle class, namely:
• Background Color
• Check Color
• Border Color
We'll show how attributes are defined and described so that the inspector can edit them properly.
In addition to attributes, user interface objects draw in a box and respond to user events such as mouse clicks and key-
board events. We'll show how to implement drawing an object's paint method as well user interaction in the mousedown,
mousedrag, and mouseup methods.
This chapter only covers basic drawing of lines and filled rectangles. But you can take advantage of a complete graphics
API called jgraphics, intended to be used in a user interface object's paint method. We discuss JGraphics in more detail
in a separate chapter. You may also find the jgraphics.h header file descriptions of the set of functions helpful.
The SDK examples contain two user interface projects – the one we'll discuss in this chapter is called uisimp and is
a version of the toggle object with a more complicated check box and user interaction. The second project is called
pictmeter∼, a more advanced object that uses audio as well as image files.
The uisimp object differs from the toggle object in a couple of ways:
• it tracks the mouse even when it isn't down and "looks excited" when the mouse passes over it
• it tracks the mouse while the user is holding the mouse down to show a sort of "depressed" appearance when
turning the toggle on
Cycling ’74
34 Anatomy of a UI Object
• the new toggle state value is sent out when the mouse is released rather than when the mouse is down. In
addition, the uisimp object tracks the mouse and does not change the state if the mouse is released outside of
the object's box
The first thing we suggest you do is build the uisimp object and test it out. Once the object is properly building, type
"uisimp" into an object box and you can try it out.
UI objects require that you include two header files, jpatcher_api.h and jgraphics.h:
#include "jpatcher_api.h"
#include "jgraphics.h"
The header file jpatcher_api.h includes data structures and accessor functions required by UI objects. The header file
jgraphics.h includes data structures and functions for drawing.
The first part of a UI object is a t_jbox, not a t_object. You should generally avoid direct access to fields of a t_jbox,
particularly when changing values, and use the accessor functions defined in jpatcher_api.h. For example, if you change
the rectangle of a box without using the accessor function jbox_set_rect(), the patcher will not be notified properly and
the screen will not update.
Following the t_jbox, you can add other fields for storing the internal state of your object. In particular, if you are going
to be drawing something using color, you will want to create attributes that reference fields holding colors in your object.
We'll show you how to do this below. Here is the declaration of the t_uisimp data structure.
typedef struct _uisimp
{
t_jbox u_box; // header for UI objects
void *u_out; // outlet pointer
long u_state; // state (1 or 0)
char u_mouseover; // is mouse over the object
char u_mousedowninside; // is mouse down within the object
char u_trackmouse; // if non-zero, track mouse when button not down
t_jrgba u_outline; // outline color
t_jrgba u_check; // check (square) color
t_jrgba u_background; // background color
t_jrgba u_hilite; // highlight color (when mouse is over and when clicking to check box)
} t_uisimp;
The t_jrgba structure defines a color with four doubles for red, green, blue, and alpha. Each component ranges from 0-1.
When red, green, and blue are all 0, the color is black; when red, green, and blue are 1, the color is white. By defining
color attributes using t_jrgba structures, you will permit the user to use the standard color picker from the inspector to
configure colors for your object.
The structure members u_mouseover and u_mousedowninside are used to signal the code that paints the toggle from
the code that handles mouse interaction. We'll discuss this more in the "interaction strategy" section below.
Cycling ’74
11.3 Initialization Routine for UI Objects 35
Once you've declared your object's struct, you'll write your initialization ( ext_main() ) routine to set up the class, declaring
methods and attributes used by UI objects.
The first addition to the class initialization of a normal Max object you need to make is a call to jbox_initclass(). This
adds standard methods and attributes common to all UI objects. Here's how you should to it:
c = class_new("uisimp", (method)uisimp_new, (method)uisimp_free, sizeof(t_uisimp), 0L, A_GIMME, 0);
c->c_flags |= CLASS_FLAG_NEWDICTIONARY;
jbox_initclass(c, JBOX_FIXWIDTH | JBOX_COLOR);
The line c->c_flags |= CLASS_FLAG_NEWDICTIONARY is required, but the flags passed to jbox_initclass –
JBOX_FIXWIDTH and JBOX_COLOR – are optional. JBOX_FIXWIDTH means that when your object is selected in
a patcher, the Fix Width menu item will be enabled to resize your object to its class's default dimensions. We'll specify
the default dimensions in a moment. JBOX_COLOR means that your object will be given a color attribute so that it can
be edited with the color picked shown by the Color... menu item. This is a way to edit a "basic" color of your object
without opening the inspector. If neither of these behaviors apply to your object, feel free to pass 0 for the flags argument
to jbox_initclass().
Next we need to bind a few standard methods. The only required method for UI objects is paint, which draws the your
object's content when its box is visible and needs to be redrawn.
class_addmethod(c, (method)uisimp_paint, "paint", A_CANT, 0);
We'll discuss the paint method in detail below. It makes use of the JGraphics API, which is described in more detail in
its own chapter.
Our uisimp toggle will respond to mouse gestures, so we will define a set of mouse handling methods.
class_addmethod(c, (method)uisimp_mousedown, "mousedown", A_CANT, 0);
class_addmethod(c, (method)uisimp_mousedrag, "mousedrag", A_CANT, 0);
class_addmethod(c, (method)uisimp_mouseup, "mouseup", A_CANT, 0);
class_addmethod(c, (method)uisimp_mouseenter, "mouseenter", A_CANT, 0);
class_addmethod(c, (method)uisimp_mouseleave, "mouseleave", A_CANT, 0);
class_addmethod(c, (method)uisimp_mousemove, "mousemove", A_CANT, 0);
class_addmethod(c, (method)uisimp_mousewheel, "mousewheel", A_CANT, 0);
mousedown is sent to your object when the user clicks on your object – in other words, when the mouse is moved over
the object and the primary mouse button is depressed. mousedrag is sent after an initial mousedown when the mouse
moves and the button is still held down from the click. mouseup is sent when the mouse button is released after a
mousedown is sent. mouseenter is sent when the mouse button is not down and the mouse moves into your object's
box. mousemove is sent – after a mouseenter – when the mouse button is not down but the mouse position changes
inside your object's box. mouseleave is sent when the mouse button is not down and the mouse position moves from
being over your object's box to being outside of it. mousewheel is sent when information about the scrollwheel on the
mouse (or scrolling from another source such as a trackpad) is transmitted while the cursor is hovering over your object.
You are not obligated to respond to any of these messages. You could, for example, only respond to mousedown and
ignore the other messages.
It might be helpful to summarize mouse messages in the following "rules" (although normally it's not necessary to think
about them explicitly):
• mousedown will always be followed by mouseup, but not necessarily by mousedrag if the button press is rapid
and there is no movement while the mouse button is pressed.
Cycling ’74
36 Anatomy of a UI Object
• You cannot count on any particular relationship between the mousedown / mousedrag / mouseup sequence and
the mouseenter / mousemove / mouseleave sequence.
After the declaration of standard methods, your object will define its own attributes. By using what we call "attribute
attributes" you can further describe attributes so that they can be appropriately displayed and edited in the inspector as
well as saved in a patcher (or not). You can also set default values for attributes that are automatically copied to your
object when it is instantiated, and mark an attribute so that your object is redrawn when its value changes.
As a convenience, we've defined a series of macros in ext_obex_util.h (which is included when your object includes
ext_obex.h) that reduce the amount of typing needed to define attributes and attribute attributes.
Most UI object attributes are offset attributes; that is, they reference a location in your object's data structure by offset
and size. As an example, uisimp has a char offset attribute called trackmouse that specifies whether the object will
change the object's appearance when the mouse moves over it. Here's how this is defined:
CLASS_ATTR_CHAR(c, "trackmouse", 0, t_uisimp, u_trackmouse);
CLASS_ATTR_STYLE_LABEL(c, "trackmouse", 0, "onoff", "Track Mouse");
CLASS_ATTR_SAVE(c, "trackmouse", 0);
The first line, CLASS_ATTR_CHAR, defines a char-sized offset attribute. If you look at the declaration of t_uisimp, you
can see that the u_trackmouse field is declared to be a char. The CLASS_ATTR_CHAR macro take five arguments.
• The first argument is the class for which the attribute is being declared.
• The second argument is the name of the attribute. You can use send a message to your object with this name
and a value and set the attribute.
• The third argument is a collection of attribute flags. For the attributes (and attribute attributes) we'll be
defining in the uisimp object, the flags will be 0, but you can use them to make attributes read-only with
ATTR_SET_OPAQUE_USER.
• The fourth argument is the name of your object's structure containing the field you want to use for the attribute
• The fifth argument is the field name you want to use for the attribute
The fourth and fifth arguments are used to calculate the offset of the beginning of the field from the beginning of the
structure. This allows the attribute to read and write the memory occupied by the field directly.
The second line, CLASS_ATTR_STYLE_LABEL, defines some attribute attributes for the trackmouse attribute. THis
macro takes five arguments as well:
• The first argument is the class for which the attribute attributes are being declared.
Cycling ’74
11.5 Defining Attributes 37
• The second argument is the name of the attribute, which should have already been defined by a
CLASS_ATTR_CHAR or similar attribute declaration
• The third argument is usually 0 – it is an attribute flags argument for the attribute attributes
• The fourth argument is the style of the attribute. "onoff" is used here for a setting in your object that will be a
toggle. By using the onoff style the trackmouse attribute will appear with a checkbox in the inspector window.
Effectively, this macro defines an attribute called "style" that is attached to the "trackmouse" attribute and set its
value to the symbol "onoff" in one step.
• The fifth argument is a string used as a descriptive label for the attribute that appears in the inspector and other
places in the Max user interface. If you don't supply a label, the attribute name will be shown. The string is used
as the value of a newly created "label" attribute attribute.
The category attribute attribute is used to organize your object's attributes in the inspector window. For the trackmouse
attribute, we use the "Behavior" category, and for the color attributes discussed below, we use "Color" – look at the
inspector category tabs for a few UI objects that come with Max for suggested standard category names. You're free to
create your own.
To define a category for a single attribute, you can use the CLASS_ATTR_CATEGORY macro:
CLASS_ATTR_CATEGORY(c, "trackmouse", 0, "Behavior");
To define a category for a series of attributes, you can use CLASS_STICKY_ATTR, which applies the current value of a
specified attribute attribute to any attributes subsequently defined, until a CLASS_STICKY_ATTR_CLEAR is set for an
attribute attribute name. CLASS_STICKY_ATTR is used in uisimp to apply the "Color" category to a set of three color
attributes.
CLASS_STICKY_ATTR(c, "category", 0, "Color");
Color attributes are defined using CLASS_ATTR_RGBA. The uisimp object defines four color attributes. Here is the
first, called bgcolor:
CLASS_ATTR_RGBA(c, "bgcolor", 0, t_uisimp, u_background);
CLASS_ATTR_DEFAULTNAME_SAVE_PAINT(c, "bgcolor", 0, "1. 1. 1. 1.");
CLASS_ATTR_STYLE_LABEL(c,"bgcolor",0,"rgba","Background Color");
The difference between CLASS_ATTR_RGBA and CLASS_ATTR_CHAR for defining an attribute is that
CLASS_ATTR_RGBA expects the name of a structure member declared of type t_jrgba rather than type char. When
set, the attribute will assign values to the four doubles that make up the components of the color.
The next line uses the CLASS_ATTR_DEFAULTNAME_SAVE_PAINT macro. This sets three things about the bg-
color attribute. First it says that the color attribute bgcolor can be assigned a default value via the object de-
faults window. So, if you don't like the standard white defined by the object, you can assign you own color for the
background color of all newly created uisimp objects. The four values 1 1 1 1 supplied as the last argument to
CLASS_ATTR_DEFAULTNAME_SAVE_PAINT specify the "standard" default value that will be used for the bgcolor
attribute in the absence of any overrides from the user.
The SAVE aspect of this macro specifies that this attribute's values should be saved with the object in a patcher. A
patcher file saves an object's class, location and connections, but it can also save the object's appearance or any other
attribute value you specify, by using the "save" attribute attribute.
The PAINT aspect of this macro provides the ability to have your object redrawn whenever this attribute (bgcolor)
changes. However, to implement auto-repainting on attribute changes, you'll need to add the following code when
initializing your class:
class_addmethod(c, (method)jbox_notify, "notify", A_CANT, 0);
The function jbox_notify() will determine whether an attribute that has caused a change notification to be sent has its
paint attribute attribute set, and if so, will call jbox_redraw(). If you write your own notify method because you want to
respond to changes in attributes or other environment changes, you ∗must∗ call jbox_notify() inside of it.
Cycling ’74
38 Anatomy of a UI Object
At the beginning of our initialization routine, we passed JBOX_COLOR as a flag to jbox_initclass(). This adds an attribute
to our object called color, which uses storage provided in the t_jbox to keep track of a color for us. The color attribute is
a standard name for the "most basic" color your object uses, and if you define it, the Color menu item in the Object menu
will be enabled when your object is selected, permitting the user to change the color without opening the inspector.
If you use JBOX_COLOR, you don't need to define the color attribute using CLASS_ATTR_RGBA – jbox_initclass() will
do it for you. However, the color attribute comes unadorned, so you are free to enhance it with attribute attributes. Here's
what uisimp does:
CLASS_ATTR_DEFAULTNAME_SAVE_PAINT(c, "color", 0, "0. 0. 0. 1.");
CLASS_ATTR_STYLE_LABEL(c,"color",0,"rgba","Check Color");
Another attribute defined for your object by jbox_initclass() is called patching_rect. It holds the dimensions of your
object's box. If you want to set a standard size for new instances of your object, you can give the patching_rect a set
of default values. Use 0 0 for the first two values (x and y position) and use the next two values to define the width and
height. We want a small square to be the default size for uisimp, so we use CLASS_ATTR_DEFAULT to assign a default
value to the patching_rect attribute as follows:
CLASS_ATTR_DEFAULT(c,"patching_rect",0, "0. 0. 20. 20.");
The UI object new instance routine is more complicated than that of a normal Max object. Each UI object is passed
a t_dictionary (a hierarchically structured collection of data accessed by symbolic names) containing the information
needed to instantiate an instance. For UI objects, data elements in the dictionary correspond to attribute values. For
example, if your object saved an attribute called "bgcolor" you will be able to access the saved value in your new instance
routine from the dictionary using the same name bgcolor.
If the instance is being created from the object palette or by the typing the name of your object into an object box, the
dictionary will be filled in with default values. If the object is being created by reading a patcher file, the dictionary will
be filled in with the saved attributes stored in the file. In most cases, you don't need to work with the dictionary directly,
unless you've added proprietary non-attribute information to your object's dictionary that you want to look for and extract.
However, you do need to pass the dictionary to some standard routines, and initialize everything in the right order.
Let's take a look at the pattern you should follow for your object's new instance routine.
We will get the dictionary that defines the object out of the arguments passed in argc, argv. (The symbol argument s is
the name of the object.) If obtaining the dictionary fails, we should return NULL to indicate we didn't make an instance.
void *uisimp_new(t_symbol *s, long argc, t_atom *argv);
{
t_uisimp *x = NULL;
t_dictionary *d = NULL;
long boxflags;
if (!(d = object_dictionaryarg(argc,argv)))
return NULL;
Cycling ’74
11.6 New Instance Routine 39
Then we need to initialize the options for our box. Our object uses the options that are not commented out.
boxflags = 0
| JBOX_DRAWFIRSTIN
| JBOX_NODRAWBOX
| JBOX_DRAWINLAST
| JBOX_TRANSPARENT
// | JBOX_NOGROW
| JBOX_GROWY
// | JBOX_GROWBOTH
// | JBOX_HILITE
// | JBOX_BACKGROUND
| JBOX_DRAWBACKGROUND
// | JBOX_NOFLOATINSPECTOR
// | JBOX_MOUSEDRAGDELTA
// | JBOX_TEXTFIELD
;
We pass the flags along with a pointer to our newly created instance and the argc, argv arguments to jbox_new(). The
name is a little misleading. jbox_new() does not instantiate your box. As we explained above, your UI object has a
t_jbox at the beginning. jbox_new() just initializes the t_jbox for you. jbox_new() doesn't know about the other stuff in
your object's data structure that comes after the t_jbox. You'll have to initialize the extra items yourself.
jbox_new((t_jbox *)x, boxflags, argc, argv);
Once jbox_new() has been called, you then assign the b_firstin pointer of your t_jbox header to point to your object.
Essentially this assigns the object that will receive messages from objects connected to your leftmost inlet (as well as
other inlets via inlets or proxies you create). This step is easily forgotten and will cause most things not to work until you
remember it. jbox_new() will obtain the attributes common to all boxes such as the patching_rect, and assign them to
your object for you.
x->u_box.b_firstin = (void *)x;
Next, you are free to initialize any members of your object's data structure, as well as declare inlets. These steps are
the same for UI objects as for non-UI objects.
x->u_mousedowninside = x->u_mouseover = x->u_state = 0;
x->u_out = intout((t_object *)x);
Once your object is in a safe initialized state, call attr_dictionary_process() if you've defined any attributes. This will find
the attributes in the dictionary your object received, then set them to the values stored in the dictionary. There is no way
to guarantee the order in which the attributes will be set. If this a problem, you can obtain the attribute values "by hand"
and assign them to your object.
Note that you do not need to call attr_dictionary_process() if you have not defined any attributes. jbox_new() will take
care of setting all attributes common to all UI objects.
attr_dictionary_process(x,d);
As the last thing to do before returning your newly created UI object, and more specifically after you've initialized
everything to finalize the appearance of your object, call jbox_ready(). jbox_ready() will paint your object, calculate the
positions of the inlets and outlets, and perform other initialization tasks to ensure that your box is a proper member of
the visible patcher.
If your object does not appear when you instantiate it, you should check whether you do not have a jbox_ready() call.
jbox_ready((t_jbox *)x);
Finally, as with any instance creation routine, the newly created object will be returned.
return x;
Cycling ’74
40 Anatomy of a UI Object
Drawing anything to the screen must be limited to your paint method (this was not the case with the previous UI object
API in Max). If you want to redraw something, you need to call jbox_redraw() to cause the screen to be redrawn. This
is necessary because your object is part of a compositing user interface that must be managed by the patcher as a
whole to avoid screen artifacts. The jbox_redraw() routine calculates the area of the screen that needs to be redrawn,
then informs the Mac or Windows "window manager" to mark this area as invalid. At some later point in time, the OS
will invoke the patcher's paint routine, which will dispatch to all of the boxes inside the invalid area according to the
current Z-order of all the boxes. Boxes that are in the background are drawn first, so that any transparent or semi-
transparent boxes can be drawn on top of them. In addition, unless you specify otherwise, the last drawn image of a box
is cached in a buffer, so that your paint method will only be called when you explicitly invalidate your object's content
with jbox_redraw(). In other words, you can't count on "global patcher drawing" to invoke your paint method.
The basic strategy you'll want to use in thinking about redrawing is that you will set internal state in other methods, then
call jbox_redraw(). The paint method will read the internal state and adjust its drawing appropriately. You'll see this
strategy used in the uisimp object as it tracks the mouse.
Your object's paint method uses the jgraphics API to draw. The header file, jgraphics.h, provides a description of each
of the routines in the API. Here we will only discuss general principles and features of drawing with uisimp's relatively
simple paint method. There is also a jgraphics example UI object that contains a number of functions showing how
various drawing tasks can be performed.
Drawing in Max is resolution-independent. The "size" of your object's rectangle is always the pixel size when the
patcher is scaled to 100% regardless of the zoom level, and any magnification or size reduction to the actual screen is
automatically handled by matrix transforms. Another thing that is handled automatically for you is drawing to multiple
views. If a patcher is invisible (i.e., a subpatcher that has not been double-clicked), it does not have any views. But if it is
visible, a patcher can have many patcherviews. If your UI object box is in a patcher with multiple views open, your paint
method will be called once for each view, and will be passed different a patcherview object each time. For most objects,
this will pose few problems, but for objects to work properly when there are anywhere from zero to ten views open, they
cannot change their internal state in the paint method, they can only read it. As an example, if your object had a boolean
"painted" field in its structure that would be set when the paint method had finished, it would not work properly in the
cases where the box was invisible or where it was shown in multiple patcher views, because it would either be set zero
or more than once.
The first step for any paint method is to obtain the t_jgraphics object from the patcherview object passed to the paint
method. The patcherview is an opaque t_object that you will use to access information about your box's rectangle and
its graphics context. A patcherview is not the same thing as a patcher; as mentioned above, there could be more than
one patcherview for a patcher if it has multiple views open.
void uisimp_paint(t_uisimp *x, t_object *patcherview)
{
t_rect rect;
t_jgraphics *g = (t_jgraphics*) patcherview_get_jgraphics(patcherview); // obtain graphics context
After obtaining the t_jgraphics object, the next thing that you'll need to do is determine the rectangle of your box. A
view of a patcher may be in either patching or presentation mode. Since each mode can have its own rectangle, it is
necessary to use the patcherview to obtain the rectangle for your object.
jbox_get_rect_for_view((t_object *)x, patcherview, &rect);
The t_rect structure specifies a rectangle using the x and y coordinates of the top left corner, along with the width and
height. However, the coordinates of the t_jgraphics you'll be using to draw into always begin at 0 for the top left corner,
so you'll only care about the width and height, at least for drawing.
Cycling ’74
11.8 The Paint Method 41
The first thing we'll draw is just an outline of our box using the value of the outline color attribute. First we'll set the color
we want to use, then make a rectangular path, then finally we'll stroke the path we've made.
With calls such as jgraphics_rectangle(), the rectangular shape is added to the existing path. The initial path is empty,
and after calling jgraphics_stroke() or jgraphics_fill(), the path is again cleared. (If you want to retain the path, you can
use the jgraphics_stroke_preserve() and jgraphics_fill_preserve variants().)
jgraphics_set_source_jrgba(g, &x->u_outline);
jgraphics_set_line_width(g, 1.);
jgraphics_rectangle(g, 0., 0., rect.width, rect.height);
jgraphics_stroke(g);
You do not need to destroy the path before your paint method is finished. This will be done for you, but the fact that the
path does not survive after the paint method is finished means you can't make a path and then store it without copying
it first. Such a strategy is not recommended in any case, since your object's rectangle might change unpredictably from
one paint method invocation to the next, which will likely cause your path to be the wrong shape or size.
The next feature of the paint method is to draw an inner outline if the mouse is moved over the box. Detecting the
mouse's presence over the box happens in the mouseenter / mouseleave methods described below – but essentially,
we know that the mouse is over our object if the u_mouseover has been set by these mouse tracking methods.
To draw a rectangle that is inset by one pixel from the box rectangle, we use the rectangle starting at 1, 1 with a width of
the box width - 2 and a height of the box height - 2.
// paint "inner highlight" to indicate mouseover
if (x->u_mouseover && !x->u_mousedowninside) {
jgraphics_set_source_jrgba(g, &x->u_hilite);
jgraphics_set_line_width(g, 1.);
jgraphics_rectangle(g, 1., 1., rect.width - 2, rect.height - 2);
jgraphics_stroke(g);
}
Some similar code provides the ability to show the highlight color when the user is about to check (turn on) the toggle:
if (x->u_mousedowninside && !x->u_state) { // paint hilite color
jgraphics_set_source_jrgba(g, &x->u_hilite);
jgraphics_rectangle(g, 1., 1., rect.width - 2, rect.height - 2);
jgraphics_fill(g);
}
Finally, we paint a square in the middle of the object if the toggle state is non-zero to indicate that the box has been
checked. Here we are filling a path instead of stroking it. Note also that we use the call jbox_get_color() to get the
"standard" color of our object that is stored inside the t_jbox. As we've specified by using the JBOX_COLOR flag for
jbox_initclass() in our initialization routine, the color obtained by jbox_get_color() for the "check" (really just a square of
solid color) is the one the user can change with the Color... item in the Object menu.
if (x->u_state) {
t_jrgba col;
jbox_get_color((t_object *)x, &col);
jgraphics_set_source_jrgba(g, &col);
if (x->u_mousedowninside) // make rect bigger if mouse is down and we are unchecking
jgraphics_rectangle(g, 3., 3., rect.width - 6, rect.height - 6);
else
jgraphics_rectangle(g, 4., 4., rect.width - 8, rect.height - 8);
jgraphics_fill(g);
}
Clearly, a quick perusal of the jgraphics.h header file will demonstrate that there is much more to drawing than we've
discussed here. But the main purpose of the uisimp paint method is to show how to implement "dynamic" graphics that
follow the mouse. Now we'll see the mouse tracking side of the story.
Cycling ’74
42 Anatomy of a UI Object
When the mouse is clicked, dragged, released, or moved inside its box, your object will receive messages. In the uisimp
example we've defined methods for most of the mouse gesture messages available, and we've implemented them to
change internal state in the object, then call jbox_redraw() to repaint the object to reflect the new state. This strategy
produces a "dynamic" appearance of a gadget users associate with a typical graphical interface – in this case a toggle
checkbox.
Let's first look at the most commonly implemented mouse gesture handler, the mousedown method that responds to
an initial click on the object. As you can see, it is very simple; it merely sets u_mousedowninside to true, then calls
jbox_redraw(), causing the box to be repainted. We've defined this toggle not to change the actual state until the mouse
is released (unlike the standard Max toggle object), but we do want to give the user some feedback on the initial mouse
down that something is going to happen. If you look back at the paint method, you can see that u_mousedowninside is
used to change the way the object is painted to give it a "pending state change" appearance that will be finalized when
the mouse is released inside the box.
void uisimp_mousedown(t_uisimp *x, t_object *patcherview, t_pt pt, long modifiers)
{
x->u_mousedowninside = true; // wouldn’t get a click unless it was inside the box
jbox_redraw((t_jbox *)x);
}
If we test the mouse position to ensure that it is inside the box when it is released, we provide the opportunity for the user
to cancel the act of toggling the state of the object by moving the cursor outside of the box before releasing the button.
To provide feedback to the user that this is going to happen, we've implemented a mousedrag method that performs
this test and redraws the object if the "mouse inside" condition has changed from its previous state. The mousedrag
message will be sent to your object as long as the mouse button is still down after an initial click and the cursor has
moved, even if the cursor moves outside of the boundaries of your object's box.
Note that, as with the paint method, we use the patcherview to get the current box rectangle. We can then test the point
we are given to see if it is inside or outside the box.
void uisimp_mousedrag(t_uisimp *x, t_object *patcherview, t_pt pt, long modifiers)
{
t_rect rect;
// test to see if mouse is still inside the object
jbox_get_rect_for_view((t_object *)x, patcherview, &rect);
// redraw if changed
if (pt.x >= 0 && pt.x <= rect.width && pt.y >= 0 && pt.y <= rect.height) {
if (!x->u_mousedowninside) {
x->u_mousedowninside = true;
jbox_redraw((t_jbox *)x);
}
} else {
if (x->u_mousedowninside) {
x->u_mousedowninside = false;
jbox_redraw((t_jbox *)x);
}
}
}
Our mouseup method uses the last value of u_mousedowninside as the determining factor for whether to toggle the
object's internal state. If u_mousedowninside is false, no state change happens. But if it is true, the state changes and
the new state value is sent out the object's outlet (inside uisimp_bang()).
if (x->u_mousedowninside) {
x->u_state = !x->u_state;
uisimp_bang(x);
x->u_mousedowninside = false;
jbox_redraw((t_jbox *)x);
}
Finally, we've implemented mouseenter, mousemove, and mouseleave methods to provide another level of "mouse
over" style highlighting for the object. Rather than changing u_mousedowninside, a u_mouseover field is set when the
Cycling ’74
11.10 Freeing a UI Object 43
mouseenter message is received, and cleared when the mouseleave method is received. And again, after this variable
is manipulated, we repaint the box with jbox_redraw().
void uisimp_mouseenter(t_uisimp *x, t_object *patcherview, t_pt pt, long modifiers)
{
x->u_mouseover = true;
jbox_redraw((t_jbox *)x);
}
void uisimp_mouseleave(t_uisimp *x, t_object *patcherview, t_pt pt, long modifiers)
{
x->u_mouseover = false;
jbox_redraw((t_jbox *)x);
}
If your object has created any clocks or otherwise allocated memory that should be freed when the object goes away,
you should handle this in the free routine. But, most importantly, you must call the function jbox_free(). If your UI
object doesn't need to do anything special in its free routine, you can pass jbox_free() as the free routine argument
to class_new() in your initialization routine. We chose not to do this, since having an actual function permits easy
modification should some memory need to be freed at some point in the future evolution of the object.
void uisimp_free(t_uisimp *x)
{
jbox_free((t_jbox *)x);
}
Cycling ’74
44 Anatomy of a UI Object
Cycling ’74
Chapter 12
File Handling
These routines permit you to search for files, show file open and save dialogs, as well as open, read, write, and close
them. The file API is based around a "path identifier" – a number that describes the location of a file. When searching or
reading a file, path identifiers can be either a folders or collectives. Path identifiers that are negative (or zero) describe
actual folders in the computer's file system, while path identifiers that are positive refer to collectives.
A basic thing you might want to do make your object accept the read message in a manner similar to existing Max
objects. If the word read is followed by no arguments, a file dialog appears for the user to choose a file. If read is
followed by an argument, your object will search for the file. If a file is found (or chosen), your object will open it and
read data from it.
First, make your object accept the read message. The simplest way to make the filename argument optional is to use
the A_DEFSYM argument type specifier. When the symbol argument is not present, Max passes your method the empty
symbol.
class_addmethod(c, (method)myobject_read, "read", A_DEFSYM, 0);
The next requirement for any method that reads files is that it must defer execution to the low-priority thread, as shown
in the following implementation, where the filename argument is passed as the symbol argument to defer.
void myobject_read(t_myobject *x, t_symbol *s)
{
defer(x, (method)myobject_doread, s, 0, NULL);
}
The myobject_doread() function compares the filename argument with the empty symbol – if the argument was not
supplied, the open_dialog() is used, otherwise, we call locatefile_extended() to search for the file. This object looks for
text files, so we use a four-character code 'TEXT' as our file type to either open or locate. File type codes define a set of
acceptable extensions. The file max-fileformats.txt permits contains standard definitions, and you can add your own by
creating a similar text file and placing it in the init folder inside the Cycling '74 folder.
void myobject_doread(t_myobject *x, t_symbol *s)
{
t_fourcc filetype = ’TEXT’, outtype;
short numtypes = 1;
char filename[MAX_PATH_CHARS];
short path;
if (s == gensym("")) { // if no argument supplied, ask for file
if (open_dialog(filename, &path, &outtype, &filetype, 1)) // non-zero: user cancelled
return;
} else {
strcpy(filename, s->s_name); // must copy symbol before calling locatefile_extended
if (locatefile_extended(filename, &path, &outtype, &filetype, 1)) { // non-zero: not found
object_error(x, "%s: not found", s->s_name);
return;
Cycling ’74
46 File Handling
}
}
// we have a file
myobject_openfile(x, filename, path);
}
To open and read files, you can use the cross-platform sysfile API. Files can be opened using a filename plus path
identifier. If successfully opened, the file can be accessed using a t_filehandle. Note that "files" inside collective files are
treated identically to regular files, with the exception that they are read-only.
First, we'll implement reading the text file whose name and path identifier are passed to myobject_openfile() using a
high-level routine sysfile_readtextfile() specifically for reading text files that handles text encoding conversion for you. If
you are reading text files, using this routine is strongly recommended since converting text encodings is unpleasant to
say the least.
void myobject_openfile(t_myobject *x, char *filename, short path)
{
t_filehandle fh;
char **texthandle;
if (path_opensysfile(filename, path, &fh, READ_PERM)) {
object_error(x, "error opening %s", filename);
return
}
// allocate some empty memory to receive text
texthandle = sysmem_newhandle(0);
sysfile_readtextfile(fh, texthandle, 0, 0); // see flags explanation below
post("the file has %ld characters", sysmem_handlesize(texthandle));
sysfile_close(fh);
sysmem_freehandle(texthandle);
}
In most situations, you will pass 0 for the final two arguments to sysfile_readtextfile(). The third argument specifies a
maximum length to read, but if the value is 0, the entire file is read in, regardless of its size. The final argument is a set
of flags specifying options for reading in the text. The options concern the conversion of line breaks, text encoding, and
the ability to add a null character to the end of the data returned.
Line breaks are converted on the basis of any line break flags. When reading text files, Max converts line breaks to
"native" format, which is
\r\n
on Windows and
\n
on the Mac; this is the behavior you get if you either pass no line break flags or use TEXT_LB_NATIVE. Other options
include TEXT_LB_MAC, TEXT_LB_UNIX, or TEXT_LB_PC.
By default, text files are converted from their source encoding to UTF-8. If you do not want this conversion to occur, you
can use the TEXT_ENCODING_USE_FILE flag. This puts the burden on determining the encoding on you, which is
probably not what you want. For example, the source text file might use UTF-16 encoding, which requires very different
parsing than an 8-bit encoding.
Finally, you can have the memory returned from sysfile_readtextfile() terminated with a NULL character if you use the
TEXT_NULL_TERMINATE flag.
Cycling ’74
12.2 Reading Data Files 47
To read data files where you do not want to do text encoding conversion or worry about line breaks, you can use
the same technique shown above for text files, but write the myobject_openfile function using sysfile_read() instead of
sysfile_readtextfile(). This example shows how to read an entire file into a single block of memory.
void myobject_openfile(t_myobject *x, char *filename, short path)
{
t_filehandle fh;
char *buffer;
long size;
if (path_opensysfile(filename, path, &fh, READ_PERM)) {
object_error(x, "error opening %s", filename);
return
}
// allocate memory block that is the size of the file
sysfile_geteof(fh, &size);
buffer = sysmem_newptr(size);
// read in the file
sysfile_read(fh, &size, buffer);
sysfile_close(fh);
// do something with data in buffer here
sysmem_freeptr(buffer); // must free allocated memory
}
Some Max objects respond to the write message to save data into a file. If there is no argument present after the word
write, a save file dialog is shown and the user specifies a file name and location. If an argument is present, it can either
specify a complete path name or a filename. In the filename case, the file is written to the current "default" directory,
which is the location where a patcher was last opened. In the full pathname case, the file is written to the location
specified by the pathname.
Here's how to implement this behavior. We'll show how to handle the message arguments, then provide text and data
file writing examples.
Message and argument handling is very similar to the way we implemented the read message above, including the use
of deferred execution.
class_addmethod(c, (method)myobject_write, "write", A_DEFSYM, 0);
void myobject_write(t_myobject *x, t_symbol *s)
{
defer(x, (method)myobject_dowrite, s, 0, NULL);
}
The myobject_dowrite() function compares the filename argument with the empty symbol – if the argument was not
supplied, saveasdialog_extended() is used to obtain the user's choice for filename and location. Our first example looks
for text files, so we use a four-character code 'TEXT' as our file type for saving. File type codes define a set of acceptable
extensions. The file max-fileformats.txt permits contains standard definitions, and you can add your own by creating a
similar text file and placing it in the init folder inside the Cycling '74 folder.
void myobject_dowrite(t_myobject *x, t_symbol *s)
{
long filetype = ’TEXT’, outtype;
short numtypes = 1;
char filename[512];
short path;
if (s == gensym("")) { // if no argument supplied, ask for file
if (saveasdialog_extended(filename, &path, &outtype, &filetype, 1)) // non-zero: user cancelled
return;
} else {
strcpy(filename, s->s_name);
path = path_getdefault();
}
myobject_writefile(x, filename, path);
}
Cycling ’74
48 File Handling
Here is the text file variant of myobject_writefile() using the high-level sysfile_writetextfile() routine. We just write a
sentence as our "text file" but your object will presumably have some text data stored internally that it will write. The
buffer passed to sysfile_writetextfile() must be NULL-terminated, and will be assumed to be UTF-8 encoded.
Note that path_createsysfile() can accept a full path in the filename argument, in which case, the path argument is
ignored. This means your object's write message can either accept a filename or full pathname and you needn't do
anything special to accept both.
void myobject_writefile(t_myobject *x, char *filename, short path)
{
char *buf = "write me into a file";
long err;
t_filehandle fh;
err = path_createsysfile(filename, path, ’TEXT’, &fh);
if (err)
return;
err = sysfile_writetextfile(fh, &buf, TEXT_LB_NATIVE);
sysfile_close(fh);
}
Here is a data file variant of myobject_writefile(). It writes a small buffer of ten numbers to a file.
void myobject_writefile(t_myobject *x, char *filename, short path)
{
char *buf[10];
long count, i;
long err;
t_filehandle fh;
// create some data
for (i = 0; i < 10; i++)
buf[i] = i + 1;
count = 10;
err = path_createsysfile(filename, path, ’TEXT’, &fh);
if (err)
return;
err = sysfile_write(fh, &count, buf);
sysfile_close(fh);
}
Cycling ’74
Chapter 13
Your object can use scripting capabilities of the patcher to learn things about its context, such as the patcher's name,
hierarchy, or the peer objects to your object in its patcher.
You can also modify a patcher, although any actions your object takes are not undoable and may not work in the runtime
version.
To obtain the patcher object containing your object, you can use the obex hash table. The obex (for "object extensions")
is, more generally, a way to store and recall data in your object. In this case, however, we are just using it in a read-only
fashion.
Note that unlike the technique discussed in previous versions of the SDK, using the obex to find the patcher works at
any time, not just in the new instance routine.
void myobject_getmypatcher(t_myobject *x)
{
t_object *mypatcher;
object_obex_lookup(x, gensym("#P"), &mypatcher);
post("my patcher is at address %lx",mypatcher);
}
The patcher is an opaque Max object. To access data in a patcher, you'll use attributes and methods.
To obtain the name of the patcher and its file path (if any), obtain attribute values as shown below.
t_symbol *name = object_attr_getsym(patcher, gensym("name"));
t_symbol *path = object_attr_getsym(patcher, gensym("filepath"));
Cycling ’74
50 Scripting the Patcher
To determine the patcher hierarchy above the patcher containing your object, you can use jpatcher_get_parentpatcher().
A patcher whose parent is NULL is a top-level patcher. Here is a loop that prints the name of each parent patcher as
you ascend the hierarchy.
t_object *parent, *patcher;
t_symbol *name;
object_obex_lookup(x, gensym("#P"), &patcher);
parent = patcher;
do {
parent = jpatcher_get_parentpatcher(parent);
if (parent) {
name = object_attr_getsym(parent, gensym("name"));
if (name)
post("%s",name->s_name)
}
} while (parent != NULL);
To obtain the first object in a patcher, you can use jpatcher_get_firstobject(). Subsequent objects are available with
jbox_get_nextobject().
If you haven't read the Anatomy of a UI Object, we'll mention that the patcher does not keep a list of non-UI objects
directly. Instead it keeps a list of UI objects called boxes, and the box that holds non-UI objects is called a newobj. The
"objects" you obtain with calls such as jpatcher_get_firstobject() are boxes. The jbox_get_object() routine can be used
to get the pointer to the actual object, whether the box is a UI object or a newobj containing a non-UI object. In the case
of UI objects such as dials and sliders, the pointer returned by jbox_get_object() will be the same as the box. But for
non-UI objects, it will be different.
Here is a function that prints the class of every object (in a box) in a patcher containing an object.
void myobject_printpeers(t_myobject *x)
{
t_object *patcher, *box, *obj;
object_obex_lookup(x, gensym("#P"), &patcher);
for (box = jpatcher_get_firstobject(patcher); box; box = jbox_get_nextobject(box)) {
obj = jbox_get_object(box);
if (obj)
post("%s",object_classname(obj)->s_name);
else
post("box with NULL object");
}
}
As an alternative to the technique shown above, you can write a callback function for use with the patcher's iteration
service. The advantage of using iteration is that you can descend into the patcher hierarchy without needing to know
the details of the various objects that may contain subpatchers (patcher, poly∼, bpatcher, etc.). If you want to iterate
only at one level of a patcher hierarchy, you can do that too.
Your iteration function is defined as follows. It will be called on every box in a patcher (and, if you specify, the patcher's
subpatchers).
long myobject_iterator(t_myobject *x, t_object *b);
The function returns 0 if iteration should continue, or 1 if it should stop. This permits you to use an iterator as a way to
search for a specific object.
Cycling ’74
13.2 Creating Objects 51
The PI_WANTBOX flag tells the patcher iterator that it should pass your iterator function the box, rather than the object
contained in the box. The PI_DEEP flag means that the iteration will descend, depth first, into subpatchers. The result
parameter returns the last value returned by the iterator. For example, if the iterator terminates early by returning a
non-zero value, it will contain that value. If the iterator function does not terminate early, result will be 0.
Assuming the iterator function receives boxes, here is an example iterator that prints out the class and scripting name
(if any) of all of the objects in a patcher. Note that the scripting name is an attribute of the box, while the class we would
like to know is of the object associated with the box.
long myobject_iterator(t_myobject *x, t_object *b)
{
t_symbol *name = object_attr_getsym(b, gensym("varname"));
t_symbol *cls = object_classname(jbox_get_object(b));
if (name)
post("%s (%s)",cls->s_name, name->s_name);
else
post("%s", cls->s_name);
return 0;
}
Much of the Max user interface is implemented using patcher scripting. For example, the inspectors are patchers in
which an inspector object has been created. The file browser window has four or five separate scripted objects in it.
Even the debug window is a dynamically scripted patcher. We point this out just to inform you that creating objects in
a patcher actually works (if you get all the details right). The xxx example object shows how to use patcher scripting to
create an "editing window" similar to the ones you see when double-clicking on a table or buffer∼ object.
Creating objects in a patcher generally requires the use of a Dictionary (see discussion of UI objects above), but there
is a convenience function newobject_sprintf() that can be used to avoid some of the complexity.
To create an object, your task is to set some attributes. In the absence of any specific values, an object's attributes will be
set to some default, but you'll probably care, at the very least, about specifying the object's location. Here is an example
that creates a toggle and metro object using a combination of attribute parse syntax and sprintf. If you're interested in
creating objects with newobject_sprintf(), it may help to examine a Max document to see some of the attribute name -
value pairs used to specify objects.
t_object *patcher, *toggle, *metro;
t_max_err err;
err = object_obex_lookup(x, gensym("#P"), &patcher);
toggle = newobject_sprintf(patcher, "@maxclass toggle
@patching_position %.2f %.2f",
x->togxpos, x-> togxpos);
metro = newobject_sprintf(patcher, "@maxclass newobj @text \"metro 400\"
@patching_position %.2f %.2f",
x->metxpos, x->metypos);
Note that to create a non-UI object, you use set the maxclass attribute to newobj and the text attribute to the contents
of the object box. Attributes can be specified in any order. Using the patching_position attribute permits you to specify
only the top-left corner and use the object's default size. For text objects, the default size is based on the default font for
the patcher.
Finally, note that newobject_sprintf() returns a pointer to the newly created box, not the newly created object inside the
box. To get the object inside the box, use jbox_get_object().
Cycling ’74
52 Scripting the Patcher
If you'd like to script the connections between two objects, you can do so via a message to the patcher. Assuming
you have the patcher, toggle, and metro objects above, you'll create an array of atoms to send the message using
object_method_typed().
t_atom msg[4], rv;
atom_setobj(msg, toggle); // source
atom_setlong(msg + 1, 0); // outlet number (0 is leftmost)
atom_setobj(msg + 2, metro); // destination
atom_setlong(msg + 3, 0); // inlet number (0 is leftmost)
object_method_typed(patcher, gensym("connect"), 4, msg, &rv);
If you want to have a hidden connection, pass an optional fifth argument that is any negative number.
To delete an object in a patcher you call object_free() on the box. As of Max 5.0.6 this will properly redraw the patcher
and remove any connected patch cords.
You can use object attribute functions to modify the appearance and behavior of objects in a patcher or the patcher
itself. Note that only a few of these attributes can be modified by the user. The C level access to attributes is much more
extensive.
Attributes whose type is object can be accessed via object_attr_getobj() / object_attr_setobj(). Attributes whose
type is char can be accessed with object_attr_getchar() / object_attr_setchar(). Attributes whose type is long can
be accessed with object_attr_getlong() / object_attr_setlong(). Attributes whose type is symbol can be accessed via
object_attr_getsym() / object_attr_setsym(). For attributes that are arrays, such as colors and rectangles, use object_←-
attr_getvalueof() / object_attr_setvalueof().
Cycling ’74
13.4 Obtaining and Changing Patcher and Object Attributes 53
Cycling ’74
54 Scripting the Patcher
To access an attribute of a non-UI object, use jbox_get_object() on the box to obtain the non-UI object first.
Cycling ’74
Chapter 14
Enhancements to Objects
Presets are a simple state-saving mechanism. Your object receives a preset message when state is being saved. You
respond by creating a message that will be sent back to your object when the preset is recalled.
For more powerful and general state-saving, use the pattr system described below.
To support saving a single integer in a preset, you can use the preset_int() convenience function. The preset_int()
function records an int message with the value you pass it in the preset, to be sent back to your object at a later time.
class_addmethod(c, (method)myobject_preset, "preset", 0);
void myobject_preset(t_myobject *x)
{
preset_int(x, x->m_currentvalue);
}
More generally, you can use preset_store(). Here is an example of storing two values (m_xvalue and m_yvalue) in a list.
preset_store("ossll", x, ob_sym(x), gensym("list"), x->m_xvalue, x->m_yvalue);
In most cases, you need only to define your object's state using Attributes and it will be ready for use with Max's pattr
system. For more complex scenarios you may also wish to investigate object_notify(), object_attach(), and the section
on Receiving Notifications.
14.3 Assistance
To show descriptions of your object's inlets and outlets while editing a patcher, your object can respond to the assist
message with a function that copies the text to a string.
class_addmethod(c, (method)myobject_assist, "assist", A_CANT, 0);
The function below has two inlets and one outlet. The io argument will be 1 for inlets, 2 for outlets. The index argument
will be 0 for the leftmost inlet or outlet. You can copy a maximum of 512 characters to the output string s. You can use
Cycling ’74
56 Enhancements to Objects
strncpy_zero() to copy the string, or if you want to format the assistance string based on a current value in the object,
you could use snprintf_zero().
void myobject_assist(t_myobject *x, void *b, long io, long index, char *s)
{
switch (io) {
case 1:
switch (index) {
case 0:
strncpy_zero(s, "This is a description of the leftmost inlet", 512);
break;
case 1:
strncpy_zero(s, "This is a description of the rightmost inlet", 512);
break;
}
break;
case 2:
strncpy_zero(s, "This is a description of the outlet", 512);
break;
}
}
Objects such as operators (+, -, etc.) and the int object have inlets that merely store values rather than performing
an operation and producing output. These inlets are labeled with a blue color to indicate they are "cold" rather than
action-producing "hot" inlets. To implement this labeling, your object can respond to the inletinfo message.
class_addmethod(c, (method)myobject_inletinfo, "inletinfo", A_CANT, 0);
If all of your object's non-left inlets are "cold" you can use the function stdinletinfo() instead of writing your own, as shown
below:
class_addmethod(c, (method)stdinletinfo, "inletinfo", A_CANT, 0);
To write your own function, just look at the index argument (which is 0 for the left inlet). This example turns the third inlet
cold. You don't need to do anything for "hot" inlets.
void myobject_inletinfo(t_myobject *x, void *b, long index, char *t)
{
if (index == 2)
*t = 1;
}
Objects such as coll and text display a text editor window when you double-click. Users can edit the contents of the
objects and save the updated data (or not). Here's how to do the same thing in your object.
First, if you want to support double-clicking on a non-UI object, you can respond to the dblclick message.
class_addmethod(c, (method)myobject_dblclick, "dblclick", A_CANT, 0);
void myobject_dblclick(t_myobject *x)
{
// open editor here
}
You'll need to add a t_object pointer to your object's data structure to hold the editor.
typedef struct _myobject
{
t_object m_obj;
t_object *m_editor;
} t_myobject;
Initialize the m_editor field to NULL in your new instance routine. Then implement the dblclick method as follows:
if (!x->m_editor)
Cycling ’74
14.5 Showing a Text Editor 57
The code above does the following: If the editor does not exist, we create one by making a "jed" object and passing our
object as an argument. This permits the editor to tell our object when the window is closed.
If the editor does exist, we set its visible attribute to 1, which brings the text editor window to the front.
To set the text of the edit window, we can send our jed object the settext message with a zero-terminated buffer of text.
We also provide a symbol specifying how the text is encoded. For best results, the text should be encoded as UTF-8.
Here is an example where we set a string to contain "Some text to edit" then pass it to the editor.
char text[512];
strcpy(text,"Some text to edit");
object_method(x->m_editor, gensym("settext"), text, gensym("utf-8"));
The title attribute sets the window title of the text editor.
object_attr_setsym(x->m_editor, gensym("title"), gensym("crazytext"));
When the user closes the text window, your object (or the object you passed as an argument when creating the editor)
will be sent the edclose message.
class_addmethod(c, (method)myobject_edclose, "edclose", A_CANT, 0);
The edclose method is responsible for doing something with the text. It should also zero the reference to the editor
stored in the object, because it will be freed. A pointer to the text pointer is passed, along with its size. The encoding of
the text is always UTF-8.
void myobject_edclose(t_myobject *x, char **ht, long size)
{
// do something with the text
x->m_editor = NULL;
}
If your object will be showing the contents of a text file, you are still responsible for setting the initial text, but you can
assign a file so that the editor will save the text data when the user chooses Save from the File menu. To assign a file,
use the filename message, assuming you have a filename and path ID.
object_method(x->m_editor, gensym("filename"), x->m_myfilename, x->m_mypath);
The filename message will set the title of the text editor window, but you can use the title attribute to override the simple
filename. For example, you might want the name of your object to precede the filename:
char titlename[512];
sprintf(titlename, "myobject: %s", x->m_myfilename);
object_attr_setsym(x->m_editor, gensym("title"), gensym(titlename));
Each time the user chooses Save, your object will receive an edsave message. If you return zero from your edsave
method, the editor will proceed with saving the text in a file. If you return non-zero, the editor assumes you have taken
care of saving the text. The general idea is that when the user wants to save the text, it is either updated inside your
object, updated in a file, or both. As an example, the js object uses its edsave message to trigger a recompile of the
Javascript code. But it also returns 0 from its edsave method so that the text editor will update the script file. Except for
the return value, the prototype of the edsave method is identical to the edclose method.
class_addmethod(c, (method)myobject_edsave, "edsave", A_CANT, 0);
long myobject_edsave(t_myobject *x, char **ht, long size)
{
// do something with the text
return 0; // tell editor it can save the text
}
Cycling ’74
58 Enhancements to Objects
Table objects can be given names as arguments. If a table object has a name, you can access the data using table_get().
Supply a symbol, as well as a place to assign a pointer to the data and the length. The following example accesses a
table called foo, and, if found, posts all its values.
long **data = NULL;
long i, size;
if (!table_get(gensym("foo"), &data, &size)) {
for (i = 0; i < size; i++) {
post("%ld: %ld",i,(*data)[i]);
}
}
You can also write data into the table. If you would like the table editor to redraw after doing so, use table_dirty(). Here's
an example where we set all values in the table to zero, then notify the table to redraw.
long **data = NULL;
long i, size;
if (!table_get(gensym("foo"), &data, &size)) {
for (i = 0; i < size; i++) {
(*data)[i] = 0;
}
table_dirty(gensym("foo"));
}
Cycling ’74
Chapter 15
Data Structures
The Max API provides a variety of useful data structures which may be used across platforms and provide basic thread-
safety.
• Quick Map : a double hash with keys mapped to values and vice-versa
• String Object : wrapper for C-strings with an API for manipulating them
Most often, the use of a particular instance of a data structure will be limited to within the confines a single class or
object you create. However, in some cases you may wish to pass structured data from one object to another. For this
purpose, Max 6 introduced facilities for passing named t_dictionary instances.
Examples, descriptions, and API documentation can be found in Dictionary Passing API .
Cycling ’74
60 Data Structures
Cycling ’74
Chapter 16
Threading
First, it can be used to implement thread protection which works in conjunction with Max's existing threading model and
is cross-platform. Thread protection prevents data corruption in the case of simultaneously executing threads in the
same application. We'll discuss the Max threading model and show you a simple example of thread protection below,
but you can often avoid the need to use thread protection by using one of the thread-safe Data Storage Max provides.
The second use of the systhread API is a cross-platform way to create and manage threads. This is an advanced feature
that very few programmers will ever need. For information on creating and managing threads look at the systhread API
header file.
Please note that this description of how Max operates is subject to change and may not apply to future versions. For
more information about the Max scheduler and low-priority queue, see the The Scheduler section.
Max (without audio) has two threads. The main or event thread handles user interaction, asks the system to redraw the
screen, processes events in the low-priority queue. When not in Overdrive mode, the main thread handles the execution
of events in the Max scheduler as well. When Overdrive is enabled, the scheduler is moved to a high-priority timer
thread that, within performance limits imposed by the operating system, attempts to run at the precise scheduler interval
set by user preference. This is usually 1 or 2 milliseconds.
The basic idea is to put actions that require precise timing and are relatively computationally cheap in the high-priority
thread and computationally expensive events that do not require precise timing in the main thread. On multi-core
machines, the high-priority thread may (or may not) be executing on a different core.
On both Mac and Windows, either the main thread or the timer thread can interrupt the other thread, even though the
system priority level of the timer thread is generally much higher. This might seem less than optimal, but it is just how
operating systems work. For example, if the OS comes to believe the Max timer thread is taking too much time, the OS
may "punish" the thread by interrupting it with other threads, even if those threads have a lower system priority.
Because either thread can be interrupted by the other, it is necessary to use thread protection to preserve the integrity of
certain types of data structures and logical operations. A good example is a linked list, which can be corrupted if a thread
in the process of modifying the list is interrupted by another thread that tries to modify the list. The Max t_linklist data
Cycling ’74
62 Threading
structure is designed to be thread-safe, so if you need such a data structure, we suggest you use t_linklist. In addition,
Max provides thread protection between the timer thread and the main thread for many of its common operations, such
as sending messages and using outlets.
When we add audio into the mix (so to speak), the threading picture gets more complicated. The audio perform routine
is run inside a thread that is controlled by the audio hardware driver. In order to eliminate excessive thread blocking
and potential race conditions, the thread protection offered inside the audio perform routine is far less comprehensive,
and as discussed in the MSP section of the API documentation, the only supported operation for perform routines to
communicate to Max is to use a clock. This will trigger a function to run inside the Max scheduler.
The Max scheduler can be run in many different threading conditions. As explained above it can be run either in the
main thread or the timer thread. When Scheduler in Audio Interrupt (SIAI) is enabled, the scheduler runs with an interval
equal to every signal vector of audio inside the audio thread. However, if the Non-Real-Time audio driver is used, the
audio thread is run inside the main thread, and if SIAI is enabled, the scheduler will also run inside the main thread. If
not, it will run either in the main thread or the timer thread depending on the Overdrive setting. (Using the Non-Real-Time
audio driver without SIAI will generally lead to unpredictable results and is not recommended.)
The easiest method for thread protection is to use critical sections. A critical section represents a region of code that
cannot be interrupted by another thread. We speak of entering and exiting a critical section, and use critical_enter() and
critical_exit() to do so.
Max provides a default global critical section for your use. This same critical section is used to protect the timer thread
from the main thread (and vice versa) for many common Max data structures such as outlets. If you call critical_enter()
and critical_exit() with argument of 0, you are using this global critical section. Typically it is more efficient to use fewer
critical sections, so for many uses, the global critical section is sufficient. Note that the critical section is recursive, so
you if you exit the critical section from within some code that is already protected, you won't be causing any trouble.
It's possible that a message sent to your object could interrupt the same message sent to your object ("myobject"). For
example, consider what happens when a button is connected to the left inlet of myobject and a metro connected to the
same inlet.
When a user clicks on the bang button, the message is sent to your object in the main thread. When Overdrive is
enabled, the metro will send a bang message to your object in the timer thread. Either could interrupt the other. If your
object performs operations on a data structure that cannot be interrupted, you should use thread protection.
Here is an example that uses the global critical section to provide thread protection for an array data structure. Assume
we have an operation array_read() that reads data from an array, and array_insert() that inserts data into the same array.
We wish to ensure that reading doesn't interrupt writing and vice versa.
long array_read(t_myobject *x, long index)
{
critical_enter(0);
result = x->m_data[index];
critical_exit(0);
return result;
Cycling ’74
16.2 Thread Protection 63
Note that all paths of your code must exit the critical region once it is entered, or the other threads in Max will never
execute.
long array_insert(t_myobject *x, long index, long value)
{
critical_enter(0);
// move existing data
sysmem_copyptr(x->m_data + index, x->m_data + index + 1, (x->m_size - x->m_index) * sizeof(long));
// write new data
x->m_data[index] = value;
critical_exit(0);
}
Cycling ’74
64 Threading
Cycling ’74
Chapter 17
Drag'n'Drop
The Max file browser permits you to drag files to a patcher window or onto objects to perform file operations.
Your object can specify the file types accepted as well as a message that will be sent when the user releases the mouse
button with the file on top of the object. UI and non-UI objects use the same interface to drag'n'drop.
Messages to support:
acceptsdrag_locked (A_CANT)
Sent to an object during a drag when the mouse is over the object in an unlocked patcher.
acceptsdrag_unlocked (A_CANT)
Sent to an object during a drag when the mouse is over the object in a locked patcher.
17.1 Discussion
Why two different scenarios? acceptsdrag_unlocked() can be thought of as an "editing" operation. For example, objects
such as pictslider accept new image files for changing their appearance when the patcher is unlocked, but not when the
patcher is locked. By contrast, sfplay∼ can accept audio files for playback in either locked or unlocked patchers, since
that is something you can do with a message (rather than an editing operation that changes the patcher).
The handlers return true if the file(s) contained in the drag can be used in some way by the object. To test the filetypes,
use jdrag_matchdragrole() passing in the drag object and a symbol for the file type. Here is list of pre-defined file types:
• audiofile
• imagefile
• moviefile
• patcher
Cycling ’74
66 Drag'n'Drop
• helpfile
• textfile
If jdrag_matchdragrole() returns true, you then describe the messages your object receives when the drag completes
using jdrag_object_add(). You can add as many messages as you wish. If you are only adding a single message, use
jdrag_object_add(). For more control over the process, and for adding more than one message, jdrag_add() can be
used. If you add more than one message, the user can use the option key to specify the desired action. By default, the
first one you add is used. If there are two actions, the option key will cause the second one to be picked. If there are
more than two, a pop-up menu appears with descriptions of the actions (as passed to jdrag_add()), and the selected
action is used.
Example:
This code shows how to respond to an audiofile being dropped on your object by having the read message sent.
if (jdrag_matchdragrole(drag, gensym("audiofile"), 0)) {
jdrag_object_add(drag, (t_object *)x, gensym("read"));
return true;
}
return false;
Your acceptsdrag handler can test for multiple types of files and add different messages.
Cycling ’74
Chapter 18
ITM
It allows users to express time in tempo-relative units as well as milliseconds, samples, and an ISO 8601 hour-minute-
second format. In addition, ITM supports one or more transports, which can be synchronized to external sources.
An ITM-aware object can schedule events to occur when the transport reaches a specific time, or find out the current
transport state.
The ITM API is provided on two different levels. The time object (t_timeobject) interface provides a higher-level way to
parse time format information and schedule events. In addition, you can use lower-level routines to access ITM objects
(t_itm) directly. An ITM object is responsible for maintaining the current time and scheduling events. There can be
multiple ITM objects in Max, each running independently of the others.
There are two kinds of events in ITM. Temporary events are analogous to Max clock objects in that they are scheduled
and fire at a dynamically assigned time. Once they have executed, they are removed from the scheduler. Permanent
events always fire when the transport reaches a specific time, and are not removed from the scheduler. The ITM-aware
metro is an example of an object that uses temporary events, while the timepoint object uses permanent events. We'll
show how to work both types using an example included in the SDK called delay2. The existing Max delay object
provides this capability, but this example shows most of the things you can do with the time object interface. To see the
complete object, look at the delay2 example. We'll introduce a simpler version of the object, then proceed to add the
quantization and the additional outlet that generates a delayed bang based on low-level ITM calls.
The ITM time object API is based on a Max object you create that packages up common ways you will be using ITM,
including attribute support, quantization, and, if you want it, the ability to switch between traditional millisecond-based
timing and tempo-based timing using an interface that is consistent with the existing Max objects such as metro and
delay. (If you haven't familiarized yourself with attributes, you may want to read through the discussion about them in
Attributes before reading further.)
To use the time object, you'll first need to provide some space in your object to hold a pointer to the object(s) you'll be
creating.
typedef struct _delay2simple
{
t_object m_ob;
t_object *m_timeobj;
void *m_outlet;
Cycling ’74
68 ITM
} _delay2simple;
Next, in your ext_main() routine, you'll create attributes associated with the time object using the class_time_addattr()
function.
class_time_addattr(c, "delaytime", "Delay Time", TIME_FLAGS_TICKSONLY | TIME_FLAGS_USECLOCK |
TIME_FLAGS_TRANSPORT);
The second argument, "delaytime", is a string that names the attribute. Users of your object will be able to change the
delay value by sending a delaytime message. "Delay Time" is the label users see for the attribute in the inspector. The
flags argument permits you to customize the type of time object you'd like. TIME_FLAGS_TICKSONLY means that the
object can only be specified in tempo-relative units. You would not use this flag if you want the object to use the regular
Max scheduler if the user specifies an absolute time (such as milliseconds). TIME_FLAGS_USECLOCK means that it
is a time object that will actually schedule events. If you do not use this flag, you can use the time object to hold and
convert time values, which you use to schedule events manually. TIME_FLAGS_TRANSPORT means that an additional
attribute for specifying the transport name is added to your object automatically (it's called "transport" and has the label
"Transport Name"). The combination of flags above is appropriate for an object that will be scheduling events on a
temporary basis that are only synchronized with the transport and specified in tempo-relative units.
The next step is to create a time object in your new instance routine using time_new. The time_new function is something
like clock_new – you pass it a task function that will be executed when the scheduler reaches a certain time (in this case,
delay2simple_tick, which will send out a bang). The first argument to time_new is a pointer to your object, the second
is the name of the attribute created via class_time_addattr, the third is your task function, and the fourth are flags to
control the behavior of the time object, as explained above for class_time_addattr.
To make a delayed bang, we need a delay2simple_bang function that causes our time object to put its task function
into the ITM scheduler. This is accomplished using time_schedule. Note that unlike the roughly equivalent clock_fdelay,
where the delay time is an argument, the time value must already be stored inside the time object using time_setvalue.
The second argument to time_schedule is another time object that can be used to control quantization of an event.
Since we aren't using quantization in this simple version of delay2, we pass NULL.
void delay2simple_bang(t_delay2 *x)
{
time_schedule(x->d_timeobj, NULL);
}
Next, our simple task routine, delay2simple_tick. After the specified number of ticks in the time object has elapsed after
the call to time_schedule, the task routine will be executed.
void delay2_tick(t_delay2 *x)
{
outlet_bang(x->d_outlet);
}
Now let's add the two more advanced features found in delay2: quantization and a second (unquantized) bang output
using low-level ITM routines. Here is the delay2 data structure. The new elements are a proxy (for receiving a delay
time), a time object for quantization (d_quantize), a clock to be used for low-level ITM scheduling, and an outlet for the
use of the low-level clock's task.
typedef struct delay2
{
t_object d_obj;
void *d_outlet;
Cycling ’74
18.2 Permanent Events 69
void *d_proxy;
long d_inletnum;
t_object *d_timeobj;
t_object *d_outlet2;
t_object *d_quantize;
void *d_clock;
} t_delay2;
In the initialization routine, we'll define a quantization time attribute to work in conjunction with the d_quantize time object
we'll be creating. This attribute does not have its own clock to worry about. It just holds a time value, which we specify
will only be in ticks (quantizing in milliseconds doesn't make sense in the ITM context). If you build delay2 and open the
inspector, you will see time attributes for both Delay Time and Quantization.
class_time_addattr(c, "quantize", "Quantization", TIME_FLAGS_TICKSONLY);
Here is part of the revised delay2 new instance routine. It now creates two time objects, plus a regular clock object.
x->d_inletnum = 0;
x->d_proxy = proxy_new(x, 1, &x->d_inletnum);
x->d_outlet2 = bangout(x);
x->d_outlet = bangout(x);
x->d_timeobj = (t_object*) time_new((t_object *)x, gensym("delaytime"), (method)delay2_tick,
TIME_FLAGS_TICKSONLY | TIME_FLAGS_USECLOCK);
x->d_quantize = (t_object*) time_new((t_object *)x, gensym("quantize"), NULL, TIME_FLAGS_TICKSONLY);
x->d_clock = clock_new((t_object *)x, (method)delay2_clocktick);
To use the quantization time object, we can pass it as the second argument to time_schedule. If the value of the
quantization is 0, there is no effect. Otherwise, time_schedule will move the event time so it lies on a quantization
boundary. For example, if the quantization value is 4n (480 ticks), the delay time is 8n (240 ticks) and current time is 650
ticks, the delay time will be adjusted so that the bang comes out of the delay2 object at 980 ticks instead of 890 ticks.
In addition to using quantization with time_schedule, delay2_bang shows how to calculate a millisecond equivalent for
an ITM time value using itm_tickstoms. This delay value is not quantized, although you read the time value from the
d_quantize object and calculate your own quantized delay if wanted. The "calculated" delay is sent out the right outlet,
since the clock we created uses delay2_clocktick.
void delay2_bang(t_delay2 *x)
{
double ms, tix;
time_schedule(x->d_timeobj, x->d_quantize);
tix = time_getticks(x->d_timeobj);
tix += (tix / 2);
ms = itm_tickstoms(time_getitm(x->d_timeobj), tix);
clock_fdelay(x->d_clock, ms);
}
void delay2_clocktick(t_delay2 *x)
{
outlet_bang(x->d_outlet2);
}
A permanent event in ITM is one that has been scheduled to occur when the transport reaches a specific time. You can
schedule a permanent event in terms of ticks or bars/beats/units. An event based in ticks will occur when the transport
reaches the specified tick value, and it will not be affected by changes in time signature. An event specified for a time
in bars/beats/units will be affected by the time signature. As an example, consider an event scheduled for bar 2, beat 1,
unit 0. If the time signature of the ITM object on which the event has been scheduled is 3/4, the event will occur at 480
times 3 or 1440 ticks. But if the time signature is 4/4, the event will occur at 1920 ticks. If, as an alternative, you had
scheduled the event to occur at 1920 ticks, setting the time signature to 3/4 would not have affected when it occurred.
You don't "schedule" a permanent event. Once it is created, it is always in an ITM object's list of permanent events. To
specify when the event should occur, use time_setvalue.
The high-level time object interface handles permanent events. Let's say we want to have a time value called "tar-
gettime." First, we declare an attribute using class_time_addattr. The flags used are TIME_FLAGS_TICKSONLY
Cycling ’74
70 ITM
(required because you can't specify a permanent event in milliseconds), TIME_FLAGS_LOCATION (which inter-
prets the bar/beat/unit times where 1 1 0 is zero ticks), TIME_FLAGS_PERMANENT (for a permanent event), and
TIME_FLAGS_TRANSPORT (which adds a transport attribute permitting a user to choose a transport object as a desti-
nation for the event) and TIME_FLAGS_POSITIVE (constrains the event to happen only for positive tick and bar/beat/unit
values).
class_time_addattr(c, "targettime", "Target Time", TIME_FLAGS_TICKSONLY | TIME_FLAGS_LOCATION |
TIME_FLAGS_PERMANENT | TIME_FLAGS_TRANSPORT | TIME_FLAGS_POSITIVE);
The TIME_FLAGS_TRANSPORT flag is particularly nice. Without any intervention on your part, it creates a transport
attribute for your object, and takes care of scheduling the permanent event on the transport the user specifies, with a
default value of the global ITM object. If you want to cause your event to be rescheduled dynamically when the user
changes the transport, your object can respond to the reschedule message as follows.
class_addmethod(c, (method)myobject_reschedule, "reschedule", A_CANT, 0); // for dynamic
transport reassignment
All you need to do in your reschedule method is just act as if the user has changed the time value, and use the current
time value to call time_setvalue.
In your new instance routine, creating a permanent event with time_new uses the same flags as were passed to class←-
_time_addattr:
x->t_time = (t_object*) time_new((t_object *)x, gensym("targettime"), (method)myobject_tick,
TIME_FLAGS_TICKSONLY | TIME_FLAGS_USECLOCK | TIME_FLAGS_PERMANENT | TIME_FLAGS_LOCATION |
TIME_FLAGS_POSITIVE);
The task called by the permanent time object is identical to a clock task or an ITM temporary event task.
18.3 Cleaning Up
With all time objects, both permanent and temporary, it's necessary to free the objects in your object's free method.
Failure to do so will lead to crashes if your object is freed but its events remain in the ITM scheduler. For example, here
is the delay2 free routine:
void delay2_free(t_delay2 *x)
{
freeobject(x->d_timeobj);
freeobject(x->d_quantize);
freeobject((t_object *) x->d_proxy);
freeobject((t_object *)x->d_clock);
}
Cycling ’74
Chapter 19
MC
MC is the system for multi-voice or multi-channel audio signals introduced with Max 8.
MC is not an entirely new API for MSP objects. Instead it is built on top of the existing MSP API. Is it implemented as
some additions to the MSP signal compiler — the code that turns a graph of MSP objects into an ordered sequence of
operations on signals — to deal with patch cords that hold more than one audio signal. Multi-channel signal patch cords
co-exist with regular old- school patch cords as well as Jitter and event patch cords.
An important principle of Max is that outlet types define patch cord types. This is how Max knows, when the user clicks
on an outlet to patch something, what kind of patch cord to make. Jitter objects have “matrix” outlets. Regular Max
events travel through outlets that either have no type defined or a type such as “int” or “bang.”
Following this principle, if you want your MSP object to have outlets that produce multi-channel signals, you will have to
change the type of the outlets from signal to multichannelsignal. An outlet of type multichannelsignal will have between
1 and 1024 signals in it. A patch cord coming from this type of outlet will be a special color and width, and can be
connected to any MSP object, even those that don’t know about MC. (In that case, only the first channel will be used.)
What are you hoping to do to your object within the MC universe? If it’s a filter, do you want N filters, one operating on
every audio channel? If that’s all you want, you can avoid doing any coding simply by whitelisting your object to use the
MC wrapper. Let’s say your object is called myfilter∼ and you want the N-way version to be called mc.myfilter∼. Add
the following message to Max in any file that is evaluated at startup (that typically means you’ll put it in the init folder):
max objectfile mc.myfilter~ mc.wrapper~ myfilter~;
This establishes a mapping when the user types mc.myfilter∼ into an object box. The MC wrapper looks at the name
the user types, removes the “mc.” from the beginning, and looks for a Max object with the string that remains. So, this
means you can’t do this:
max objectfile myNWAYfilter~ mc.wrapper~ myfilter~;
The name “myfilter∼” at the end of this message specifies the name of the help file to open. If you want to make a
special help file for the N-way version of your object, you could do this:
max objectfile mc.myfilter~ mc.wrapper~ mc.myfilter~;
Finally, you may provide an optional fourth argument to the objectfile message that specifies the tab to open in the help
patcher. For example, when you open the help patcher for the mcs.limi∼ object in Max the limi∼ help patcher is opened
to the tab named "mcs". This done using the pattern from this code:
max objectfile mc.myfilter~ mc.wrapper~ mc.myfilter~ <optional-helpfile-tabname>;
Cycling ’74
72 MC
Maybe your concept of MC compatibility is not related to having N copies of your object in the wrapper. For example,
your object might be concerned with audio signal input or output, either to the outside world or to a file. Perhaps you
simply want to accept all inputs as a single multi- channel signal (which is nice if you don’t want to decide in advance how
many channels you will accept). Perhaps you want to produce a multi-channel signal instead of separate single-channel
signals. Maybe your object mixed some stuff together that you realize would be nice not to mix together, so you’d like to
provide each unmixed audio output together in a multi-channel patch cord.
For these cases, you can use the extensions to the MSP API described here. An MSP object that is MC-compatible will
work in any version of Max with 64-bit floating-point. By convention, MSP objects that operate in both single-channel
and multi-channel versions look at the object name symbol passed to the new instance routine and are multi-channel if
the name begins with “mc.” or “mcs.” You are free to rebel against this standard and establish your own convention.
There are no special functions exported from Max or the MSP library specific to MC, so the only thing that will break
in Max 7 and earlier versions is that your outlets won’t work because only Max 8 knows about the multichannelsignal
outlet type.
For multi-channel inlets, you don’t need to do anything special unless you want fewer inlets that you would otherwise.
Just call dsp_setup() as you normally would and the user will be able to connect both single-channel and multi-channel
patch cords. For multi-channel outlets, instead of
outlet_new(x, "signal");
use
outlet_new(x, "multichannelsignal");
Most of what you have to do is related to being a bit more careful about what you might previously have been able to
assume about your perform method.
The prototype for your MSP perform method looks like this:
void myobject_perform64(t_myobject *x, t_object *dsp64, double **ins, long numins, double **outs, long numouts,
long sampleframes, long flags, void *userparam);
Let’s say your object will have one multi-channel signal input and one multi-channel signal output. As you can probably
guess, the numins parameter to the perform method will be the count of channels in the input and the numouts parameter
will be the count of channels in the output. Now let’s consider some other cases because once we have more than one
inlet and/or outlet, things get trickier.
Consider an object with two multi-channel inputs. You don’t know in advance how many channels will be in each signal
connected to your object. It could be 1 (if the user connects an old-style patch cord). It could be 100. It could be there
is no connection at all to your object. What do you do?
If you’re in this situation, you’ll need to ask MSP how many channels are in each input in your dsp64 method, which is
called before the DSP is turned on. The prototype for your dsp64 method looks like this:
void myobject_dsp64(t_myobject *x, t_object *dsp64, short *count, double samplerate, long maxvectorsize, long
flags);
Cycling ’74
19.5 Specifying Output Channel Counts 73
The dsp64 object passed to this method can be used to interrogate the number of channels in each of your object’s
inlets via the getnuminputchannels method. If your object has two inlets, here is how you can find out how many input
channels each inlet has:
void myobject_dsp64(t_myobject *x, t_object *dsp64, short *count, double samplerate, long maxvectorsize, long
flags)
{
long leftinletchannelcount, rightinletchannelcount;
leftinletchannelcount = (long)object_method(dsp64,gensym("getnuminputchannels"), x, 0);
rightinletchannelcount = (long)object_method(dsp64, gensym("getnuminputchannels"), x, 1);
}
Note that an unconnected inlet has one channel, which, as has always been in the case in MSP, will be a signal
containing all zeroes.
You might want to store these channel count values in your object so you can make use of them in your perform method.
Then you’ll know how to interpret the ins array of audio buffers you receive.
The channel count for a multi-channel signal outlet is determined when MSP is building the DSP chain with the signal
compiler. This means it can change each time the user turns the audio on if the graph has changed.
In MC, it’s important to remember that outlets, not inlets, determine signal channel counts. You report the number of
channels your object’s outlets will have by supporting the multichanneloutputs method.
class_addmethod(c, (method)myobject_multichanneloutputs, "multichanneloutputs", A_CANT, 0);
...
long myobject_multichanneloutputs(t_myobject *x, long outletindex)
{
return 4;
}
If your object has defined multi-channel outlets it may receive the inputchanged message when the MSP signal compiler
runs. This notifies your object how many channels are going to be sent to one of your object’s inlets. You don’t have to
implement an inputchanged method, but you can use the information it provides to auto-adapt your object’s number of
output channels in one or more of your multi-channel signal outlets. The MC Wrapper performs auto- adapting when
the user does not specify a fixed number of channels. For example, if "mc.cycle∼ 440 \@chans 64" is connected to the
input of mc.∗∼, the wrapper will create 64 instances of a ∗∼ object, one to multiply the output of each of the 64 cycle∼
objects in the mc.cycle∼.
Auto-adapting is a “conversational” protocol that involves both the inputchanged and multichanneloutputs methods.
Cycling ’74
74 MC
Index is the inlet index of your object (with the leftmost being zero) and count is the number of audio channels in that
particular inlet.
Your object’s inputchanged method should return true if its idea of how many outputs one of your outlets may be
changing based on the information just received. It should return false it is not going to change. Returning false is polite
and optimizes the speed of compiling the signal chain.
You should also store the count you receive somewhere in your object if you are going to use it to modify the count of
output channels. After you return true from the inputchanged method your object’s multichanneloutputs method will be
called for every multichannelsignal outlet your object has created. You can then return the new channel count based on
the information received in the inputchanged method.
Here’s an illustration of the auto-adapting protocol with a simple example of an object with one inlet and one multichan-
nelsignal outlet. First, the object will receive the inputchanged method:
long myobject_inputchanged(t_myobject *x, long index, long count)
{
if (count != x->m_inputcount) {
x->m_inputcount = count;
return true;
}
else
return false;
}
long myobject_multichanneloutputs(t_myobject *x, long index)
{
return x->m_inputcount;
}
Note that the inputchanged and multichanneloutputs methods will be sent to your object before the dsp64 method.
It’s a good practice to use the getnuminputchannels technique inside your object’s dsp64 method as demonstrated
above even if you support the inputchanged method. The signal compiler is not guaranteed to send the inputchanged
message to your object in all cases — for example, it may not send inputchanged if there is nothing connected to one of
your inlets, so for determining the output count you should assume one channel until inputchanged tells you something
else. (Currently the inputchanged message is sent to objects with unconnected inlets, but this adds some overhead, so
we’re investigating whether it’s always necessary.)
If you want to determine the count of signal output channels your object has for any of its multi- channel (or single-
channel) outlets in your dsp64 method, you can send the message getnumoutputchannels to the dsp64 object:
long leftoutletchannelcount = (long)object_method(dsp64, gensym("getnumoutputchannels"), x, 0);
The getnumoutputchannels method of the dsp64 object works the same as the getnuminputchannels method.
Here’s something potentially unintuitive about the MC signal compiler: it always gives you the number of output channels
you want, whether or not that’s a good idea. Here’s what this means. Suppose you have an object with two multichan-
nelsignal outlets. In your dsp64 method, you notice that one of these outlets is not connected to anything (its entry in
the count[] array is zero). You might assume this means the output channel count is zero, or maybe one (as with the
number of channels given to an unconnected inlet). However, in reality it will be the number of channels you returned
for this outlet in your multichanneloutputs method. The principle that applies here, established since the first version of
MSP, is that you should never have to modify the behavior of your perform method based on whether its outputs are
connected. You could choose not to call dsp_add64 in a case where none of your object’s outlets were connected in
which case your perform method won’t be called. But if the perform method is going to be called, it will receive the
number of outputs you request.
To summarize, if your object has two outlets and it has returned a value of 12 for each outlet in its multichanneloutputs
method, the perform method will receive a total of 24 output channels (in other words, the numouts parameter will be
24).
Cycling ’74
19.8 Handling MC Signals in Traditional MSP Objects 75
When an MC signal is connected to to a traditional MSP object, then only the first channel of a multi-channel patch cord
is handed to the object.
If you want to modify this behavior and receive all of the channels the you must supply the Z_MC_INLETS flag. Having
supplied this flag, a user can connect single-channel patch cords, multi-channel patch cords, or both – and you’ll have
to make the best out of the situation.
In addition to the helpfile tab argument in objectmappings mentioned above, you may wish to provide additional help
patchers for your object.
When a user asks to open a help patcher for an object and there is only one help patcher available then that one help
patcher is opened immediately. If a user asks to open a help patcher for an object with multiple help patchers then a
menu is provided and the user selects the appropriate help patcher. Support for multiple help patchers was added in
Max 8 to support MC. Specifically, the features of the MC wrapper have thier own help patcher which can then be shared
across multiple objects.
As an example, the mc.limi∼ object uses the wrapper. To make the shared wrapper help patcher available as an option
for mc.limi∼, the following line is placed in an init file:
max classnamehelpcategory mc.limi~ mcwrap;
The category mcwrap is defined with a line also in the init file which looks like this:
max categoryhelp mcwrap mcwrapper-group "MC Wrapper Features Help";
The first argument to "categoryhelp" is the name of the category to create. The second argument is the name of a help
patcher in the searchpath (minus its suffix). The third argument is the string that will appear in the menu.
To see the existing categories and mappings, look at the file named "mc-helpconfig.txt " inside the application bundle's
init folder.
19.10 Examples
The first example is a signal visualizer called gridmeter∼. It is included to demonstrate just two changes for objects
receiving multi-channel inputs:
• First, the object checks the value returned by the getnuminputchannels method in the dsp64 method. This permits
it to know how many channels to paint in the grid.
• Second, it does not assume a specific count of channels in its perform method, which was typically the case with
meter objects in MSP. Instead, it has a loop for each input channel up to the value of the numins parameter.
The second example is called mc.rotate∼ and demonstrates how to implement the auto-adapting protocol. mc.rotate∼
simply rotates all the channels in any multi-channel signal it receives by one, but it will produce the same number of
output channels as the number of inputs it receives.
Finally, there is also the example of mc.pack∼ which takes only the first channel of any connected multi-channel signal.
But if you type mc.combine∼ instead it uses all the channels. mc.pack∼ and mc.combine∼ are the same object, but
behave differently based on the convention that something called “pack” doesn’t produce more outputs than it has inlets.
This demonstrates the use of the Z_MC_INLETS flag.
Cycling ’74
76 MC
Cycling ’74
Chapter 20
Jitter objects use an object model which is somewhat different than the one traditionally used for developing Max external
objects. The first big difference between Jitter objects and traditional Max external objects is that Jitter objects don't have
any notion of the patcher themselves. This allows for the flexible instantiation and use of Jitter objects from C, Java,
JavaScript, as well as in the Max patcher. The use of these Jitter objects is exposed to the patcher with a Max "wrapper"
object, which will be discussed in the following chapter.
In this chapter we'll restrict our discussion to the fundamentals of defining the Jitter object which can be used in any
of these languages. While Jitter's primary focus is matrix processing and real-time graphics, these tasks are unrelated
to the object model, and will be covered in later chapters on developing Matrix Operator (MOP) and OB3D objects.
Like Max objects, Jitter objects are typically written in C. While C++ can be used to develop Jitter objects, none of the
object oriented language features will be used to define your object as far as Jitter is concerned. Similar to C++ or Java
objects, Jitter objects are defined by a class with methods and member variables - we will refer to the member variables
as "attributes". Unlike C++ or Java, there are no language facilities that manage class definition, class inheritance, or
making use of class instances. In Jitter this must all be managed with sets of standard C function calls that will define
your class, exercise methods, and get and set object attributes.
Max and Jitter implement their object models by maintaining a registry of ordinary C functions and struct members that
map to methods and attributes associated with names. When some other code wishes to make use of these methods
or attributes, it asks the Jitter object to look up the method or attribute in its registry based on a name. This is called
dynamic binding, and is similar to Smalltalk or Objective C's object model. C++ and Java typically make use of static
binding — i.e. methods and member variables are resolved at compile time rather than being dynamically looked up at
run time.
A Jitter class is typically defined in a C function named something like your_object_name_init(). Class definition be-
gins with a call to jit_class_new(), which creates a new class associated with a specified name, constructor, destruc-
tor, and size in bytes of the object as stored in a C structure. This is followed by calls to jit_class_addmethod() and
jit_class_addattr(), which register methods and attributes with their corresponding names in the class. The class is
finally registered with a call to jit_class_register(). A minimal example class definition is shown below:
typedef struct _jit_foo
Cycling ’74
78 Jitter Object Model
{
t_jit_object ob;
float myval;
} t_jit_foo;
static t_jit_class *_jit_foo_class=NULL;
t_jit_err jit_foo_init(void)
{
long attrflags=0;
t_jit_object *attr;
// create new class named "jit_foo" with constructor + destructor
_jit_foo_class = jit_class_new("jit_foo",(method)jit_foo_new,
(method)jit_foo_free, sizeof(t_jit_foo), 0L);
// add method to class
jit_class_addmethod(jit_foo_scream, "scream", A_DEFLONG, 0L);
// define attribute
attr = jit_object_new( // instantiate an object
_jit_sym_jit_attr_offset, // of class jit_attr_offset
"myval", // with name "myval"
_jit_sym_float32, // type float32
attrflags, // default flags
(method)0L, // default getter accessor
(method)0L, // default setter accessor
calcoffset(t_jit_foo,myval)); // byte offset to struct member
// add attribute object to class
jit_class_addattr(_jit_foo_class, attr);
// register class
jit_class_register(_jit_foo_class);
return JIT_ERR_NONE;
}
// constructor
t_jit_foo *jit_foo_new(void)
{
t_jit_foo *x;
// allocate object
if (x=jit_object_alloc(_jit_foo_class))
{
// if successful, perform any initialization
x->myval = 0;
}
return x;
}
// destructor
void jit_foo_free(t_jit_foo *x)
{
// would free any necessary resources here
}
// scream method
void jit_foo_scream(t_jit_foo *x, long i)
{
post("MY VALUE IS %f! AND MY ARGUMENT IS %d", x->myval, i);
}
The above example has a constructor, jit_foo_new(); a destructor, jit_foo_free(); one 32 bit floating point attribute, myval,
a member of the object struct accessed with default accessor methods; and a method jit_foo_scream(), which posts the
current value of myval to the Max window.
Each instance of an object occupies some region of organized memory. The C structure that defines this organization
of memory is typically referred to as the "object struct". It is important that the object struct always begin with an entry
of type t_jit_object. It is within the t_jit_object where special information about the class is kept. The C structure can
contain additional information, either exposed as attributes or not, but it is important that the size of the object struct
does not exceed 16384 bytes. This means that it is not safe to define a large array as a struct entry if it will cause the
size of the object struct to be larger than this limit. If additional memory is required, the object struct should contain a
pointer to memory allocated from within the constructor, and freed within the destructor.
The class registration in the above code makes use of the object struct both to record in the class how large each
object instance should be—i.e. sizeof(t_jit_foo) ; and at what byte offset in the object struct an attribute is located—i.e.
Cycling ’74
20.4 Constructor/Destructor 79
calcoffset(t_jit_foo, myval) . When methods of an object are called, the instance of the object struct is passed as the first
argument to the C functions which define the object methods. This instance may be thought of as similar to the "this"
keyword used in C++ and Java - actually the C++ and Java underlying implementation works quite similarly to what has
been implemented here in pure C. Object struct entries may be thought of as similar to object member variables, but
methods must be called via functions rather than simply dereferencing instances of the class as you might do in C++ or
Java. The list of object methods and other class information is referenced by your object's t_jit_object entry.
20.4 Constructor/Destructor
The two most important methods that are required for all objects are the constructor and the destructor. These are typ-
ically named your_object_name_new(), and your_object_name_free(), respectively. It is the constructor's responsibility
to allocate and initialize the object struct and any additional resources the object instance requires. The object struct is
allocated via jit_object_alloc(), which also initializes the t_jit_object struct entry to point at your relevant class informa-
tion. The class information resides in your global class variable, e.g. _jit_foo_class, which you pass as an argument to
jit_object_alloc(). This allocation does not, however initialize the other struct entries, such as "myval", which you must
explicitly initialize if your allocation is successful. Note that because the constructor allocates the object instance, no
object instance is passed as the first argument to the function which defines the constructor, unlike other object methods.
The constructor also has the option of having a typed argument signature with the same types as defined in the Writing
Max Externals documentation—i.e. A_LONG, A_FLOAT, A_SYM, A_GIMME, etc. Typically, Jitter object constructors
either have no arguments or use the A_GIMME typed argument signature.
In earlier versions of Jitter, the constructors were often specified as private and "untyped" using the A_CANT type
signature. While this obsolete style of an untyped constructor will work for the exposure of a Jitter class to the patcher
and C, it is now discouraged, as there must be a valid type signature for exposure of a class to Javascript or Java,
though that signature may be the empty list.
It is the destructor's responsibility to free any resources allocated, with the exception of the object struct itself. The
object struct is freed for you after your destructor exits.
20.5 Methods
You can define additional methods using the jit_class_addmethod() function. This example defines the scream method
associated with the function jit_foo_scream(), with no additional arguments aside from the standard first argument of a
pointer to the object struct. Just like methods for ordinary Max objects, these methods could have a typed argument sig-
nature with the same types as defined in the Writing Max Externals documentation — i.e. A_LONG, A_FLOAT, A_SYM,
A_GIMME. Typically in Jitter objects, public methods are specified either without arguments, or use A_GIMME, or the
low priority variants, A_DEFER_LOW, or A_USURP_LOW, which will be discussed in following chapters. Private meth-
ods, just like their Max equivalent should be defined as untyped, using the A_CANT type signature. Object methods can
be called from C either by calling the C function directly, or by using jit_object_method() or jit_object_method_typed().
For example, the following calls that relate to the above jit_foo example are equivalent:
// call scream method directly
jit_foo_scream(x, 74);
// dynamically resolve and call scream method
jit_object_method(x, gensym("scream"), 74);
// dynamically resolve and call scream method with typed atom arguments
t_atom a[1];
jit_atom_setlong(a, 74);
jit_object_method_typed(x, gensym("scream"), 1, a, NULL);
What the jit_object_method() and jit_object_method_typed() functions do is look up the provided method symbol in the
object's class information, and then calls the corresponding C function associated with the provided symbol. The dif-
ference between jit_object_method() and jit_object_method_typed() is that jit_object_method() will not require that the
Cycling ’74
80 Jitter Object Model
method is typed and public, and blindly pass all of the arguments following the method symbol on to the corresponding
method. For this reason, it is required that you know the signature of the method you are calling, and pass the cor-
rect arguments. This is not type checked at compile time, so you must be extremely attentive to the arguments you
pass via jit_object_method(). It is also possible for you to define methods which have a typed return value with the
A_GIMMEBACK type signature. When calling such methods, the final argument to jit_object_method_typed(), should
point to a t_atom to be filled in by the callee. This and the subject of "typed wrappers" for exposing otherwise private
methods to language bindings that require typed methods (e.g. Java/!JavaScript) will be covered in a later chapter.
20.6 Attributes
You can add attributes to the class with jit_class_addattr(). Attributes themselves are Jitter objects which share a
common interface for getting and setting values. While any class which conforms to the attribute interface could be
used to define attributes of a given class, there are a few common classes which are currently used: jit_attr_offset(),
which specifies a scalar attribute of a specific type (char, long, float32, float64, symbol, or atom) at some byte offset in
the object struct; jit_attr_offset_array() which specifies an array (vector) attribute of a specific type (char, long, float32,
float64, symbol, or atom) at some byte offset in the object struct; and jit_attribute, which is a more generic attribute
object that can be instantiated on a per object basis. We will not document the usage of jit_attribute at this time. The
constructor for the class jit_attr_offset() has the following prototype:
t_jit_object *jit_attr_offset_new(char *name, t_symbol *type, long flags,
method mget, method mset, long offset);
When this constructor is called via jit_object_new(), additionally the class name, _jit_sym_jit_attr_offset (a global variable
equivalent to gensym("jit_attr_offset") ) must be passed as the first parameter, followed by the above arguments, which
are passed on to the constructor. The name argument specifies the attribute name as a null terminated C string. The
type argument specifies the attribute type, which may be one of the following symbols: _jit_sym_char, _jit_sym_long,
_jit_sym_float32, _jit_sym_float64, _jit_sym_symbol, _jit_sym_atom, _jit_sym_object, or _jit_sym_pointer. The latter
two are only useful for private attributes as these types are not exposed to, or converted from Max message atom
values.
The flags argument specifies the attribute flags, which may be a bitwise combination of the following constants:
#define JIT_ATTR_GET_OPAQUE 0x00000001 // cannot query
#define JIT_ATTR_SET_OPAQUE 0x00000002 // cannot set
#define JIT_ATTR_GET_OPAQUE_USER 0x00000100 // user cannot query
#define JIT_ATTR_SET_OPAQUE_USER 0x00000200 // user cannot set
#define JIT_ATTR_GET_DEFER 0x00010000 // (deprecated)
#define JIT_ATTR_GET_USURP 0x00020000 // (deprecated)
#define JIT_ATTR_GET_DEFER_LOW 0x00040000 // query in low priority
#define JIT_ATTR_GET_USURP_LOW 0x00080000 // query in low, usurping
#define JIT_ATTR_SET_DEFER 0x01000000 // (deprecated)
#define JIT_ATTR_SET_USURP 0x02000000 // (deprecated)
#define JIT_ATTR_SET_DEFER_LOW 0x04000000 // set at low priority
#define JIT_ATTR_SET_USURP_LOW 0x08000000 // set at low, usurping
Typically attributes in Jitter are defined with flags JIT_ATTR_GET_DEFER_LOW, and JIT_ATTR_SET_USURP_LOW.
This means that multiple queries from the patcher will generate a response for each query, and that multiple attempts
to set the value at high priority will collapse into a single call with the last received value. For more information on defer
and usurp, see the chapter on Jitter scheduling issues.
The mget argument specifies the attribute "getter" accessor method, used to query the attribute value. If this argument
is zero (NULL), then the default getter accessor will be used. If you need to define a custom accessor, it should have a
prototype and form comparable to the following custom getter:
t_jit_err jit_foo_myval_get(t_jit_foo *x, void *attr, long *ac, t_atom **av)
{
if ((*ac)&&(*av)) {
//memory passed in, use it
} else {
//otherwise allocate memory
*ac = 1;
if (!(*av = jit_getbytes(sizeof(t_atom)*(*ac)))) {
Cycling ’74
20.7 Array Attributes 81
*ac = 0;
return JIT_ERR_OUT_OF_MEM;
}
}
jit_atom_setfloat(*av,x->myval);
return JIT_ERR_NONE;
}
Note that getters require memory to be allocated, if there is not memory passed into the getter. Also the attr argument is
the class' attribute object and can be queried using jit_object_method() for things like the attribute flags, names, filters,
etc.. The mset argument specifies the attribute "setter" accessor method, used to set the attribute value. If this argument
is zero (NULL), then the default setter accessor will be used. If we need to define a custom accessor, it should have a
prototype and form comparable to the following custom setter:
t_jit_err jit_foo_myval_set(t_jit_foo *x, void *attr, long ac, t_atom *av)
{
if (ac&&av) {
x->myval = jit_atom_getfloat(av);
} else {
// no args, set to zero
x->myval = 0;
}
return JIT_ERR_NONE;
}
The offset argument specifies the attribute's byte offset in the object struct, used by default getters and setters to
automatically query and set the attribute's value. If you have both custom accessors, this value is ignored. This can
be a useful strategy to employ if you wish to have an object attribute that does not correspond to any actual entry in
your object struct. For example, this is how we implement the time attribute of jit.movie — i.e. it uses a custom getter
and setter which make QuickTime API calls to query and set the current movie time, rather than manipulating the object
struct itself, where no information about movie time is actually stored. In such an instance, you should set this offset to
zero.
After creating the attribute, it must be added to the Jitter class using the jit_class_addattr() function:
t_jit_err jit_class_addattr(void *c, t_jit_object *attr);
To put it all together: to define a jit_attribute_offset() with the custom getter and setter functions defined above, you'd
make the following call:
long attrflags = JIT_ATTR_GET_DEFER_LOW | JIT_ATTR_SET_USURP_LOW;
t_jit_object *attr = jit_object_new(_jit_sym_jit_attr_offset, "myval", _jit_sym_float32, attrflags,
(method)jit_foo_myval_get, (method)jit_foo_myval_set, NULL);
jit_class_addattr(_jit_foo_class, attr);
And to define a completely standard jit_attribute_offset(), using the default getter and setter methods:
long attrflags = JIT_ATTR_GET_DEFER_LOW | JIT_ATTR_SET_USURP_LOW;
t_jit_object *attr = jit_object_new(_jit_sym_jit_attr_offset, "myval", _jit_sym_float32, attrflags,
(method)NULL, (method)NULL, calcoffset(t_jit_foo, myval));
jit_class_addattr(_jit_foo_class, attr);
Attributes can, in addition to referencing single values, also refer to arrays of data. The class jit_attribute_offset_array is
used in this instance. The constructor for the class jit_attr_offset_array() has the following prototype:
t_jit_object *jit_attr_offset_array_new(char *name, t_symbol *type, long size,
long flags, method mget, method mset, long offsetcount, long offset);
When this constructor is called via jit_object_new(), additionally the class name, _jit_sym_jit_attr_offset_array() (a global
variable equivalent to gensym("jit_attr_offset_array") ) must be passed as the first parameter, followed by the above
arguments, which are passed on to the constructor.
The name, type, flags, mget, mset and offset arguments are identical to those specified above.
Cycling ’74
82 Jitter Object Model
The size argument specifies the maximum length of the array (the allocated size of the array in the Jitter object struct).
The offsetcount specifies the byte offset in the object struct, where the actual length of the array can be queried/set.
This value should be specified as a long. This value is used by default getters and setters when querying and setting
the attribute's value. As with the jit_attr_offset object, if you have both custom accessors, this value is ignored.
The following sample listing demonstrates the creation of a simple instance of the jit_attr_offset_array() class for an
object defined as:
typedef struct _jit_foo
{
t_jit_object ob;
long myarray[10]; // max of 10 entries in this array
long myarraycount; // actual number being used
} t_jit_foo;
long attrflags = JIT_ATTR_GET_DEFER_LOW | JIT_ATTR_SET_USURP_LOW;
t_jit_object *attr = jit_object_new(_jit_sym_jit_attr_offset_array, "myarray",
_jit_sym_long, 10, attrflags, (method)0L, (method)0L,
calcoffset(t_jit_foo, myarraycount), calcoffset(t_jit_foo, myarray));
jit_class_addattr(_jit_foo_class, attr);
Although the subject of object registration and notification will be covered in greater depth in a forthcoming chapter,
it bears noting that attributes of all types (e.g. jit_attr_offset, jit_attr_offset_array and jit_attribute) will, if registered,
automatically send notifications to all attached client objects, each time the attribute's value is set.
Cycling ’74
Chapter 21
In order to expose the Jitter object to the Max patcher, a Max "wrapper" class must be defined. For simple classes, this
is largely facilitated by a handful of utility functions that take a Jitter class and create the appropriate wrapper class with
default functionality. However, there are occasions which warrant additional intervention to achieve special behavior,
such as the use of additional inlets and outlets, integrating with MSP, converting matrix information to and from Max
lists, etc. The first Max wrapper class we'll demonstrate won't have any extra complication beyond simply containing a
basic Jitter class.
In general it is preferable to design the Jitter class so that it knows nothing about the Max patcher, and that any logic
necessary to communicate with the patcher is maintained in the Max wrapper class. In situations where this might seem
difficult, this can typically be accomplished by making special methods in the Jitter class that are only meant to be called
by the Max wrapper, or by using Jitter's object notification mechanism, which we'll discuss in a future chapter. Below is
the minimal Max wrapper class for the minimal Jitter class shown in the last chapter.
typedef struct _max_jit_foo
{
t_object ob;
void *obex;
} t_max_jit_foo;
void *class_max_jit_foo;
void ext_main(void *r)
{
void *p,*q;
// initialize the Jitter class
jit_foo_init();
// create the Max class as documented in Writing Max Externals
setup(&class_max_jit_foo,
(method) max_jit_foo_new,
(method) max_jit_foo_free,
(short)sizeof(t_max_jit_foo),
0L, A_GIMME, 0);
// specify a byte offset to keep additional information
p = max_jit_classex_setup(calcoffset(t_max_jit_foo, obex));
// look up the Jitter class in the class registry
q = jit_class_findbyname(gensym("jit_foo"));
// wrap the Jitter class with the standard methods for Jitter objects
max_jit_classex_standard_wrap(p, q, 0);
// add an inlet/outlet assistance method
addmess((method)max_jit_foo_assist, "assist", A_CANT,0);
}
void max_jit_foo_assist(t_max_jit_foo *x, void *b, long m, long a, char *s)
{
// no inlet/outlet assistance
}
void max_jit_foo_free(t_max_jit_foo *x)
{
Cycling ’74
84 Jitter Max Wrappers
The first thing you must do is define your Max class object struct. As is typical, for standard Max objects the first entry
of the object struct must be of type t_object; for UI objects, it must be of type t_jbox; for MSP objects, it must be of
type t_pxobject; and for MSP UI objects, it must be of type t_pxjbox. For more information on these different Max object
types, please consult the Max developer documentation. Jitter objects can be wrapped within any of these object types.
You also need to define a pointer to point to extra information and resources needed to effectively wrap your Jitter class.
This is typically referred to as the "obex" data, and it is where Jitter stores things like attribute information, the general
purpose "dumpout", the internal Jitter object instance, Matrix Operator resources for inlets/outlets, and other auxiliary
object information that is not required in a simple Max object. As of Max 4.5 there is also the facility for making use of
such additional object information for ordinary Max objects. At the time of this writing, such information is provided in the
Pattr developer documentation, as it is relevant to the definition of object attributes, which may be stored and operated
upon by the patcher attribute suite of objects.
In your Max class registration, which takes place in your external's ext_main() function, you should begin by calling your
Jitter class's registration function, typically named something like your_object_name_init(). Then you should proceed
to define the Max class's constructor, destructor, object struct size, and typed arguments as is typically accomplished
for Max objects via the setup function. In order for your wrapper class to be able to find the obex data, you need
to specify a byte offset where this pointer is located within each object instance and allocate the resource in which
this is stored in your Max class. This is accomplished with the max_jit_classex_setup() function. You should then
Cycling ’74
21.4 Constructor 85
look up the Jitter class via jit_class_findbyname(), and wrap it via the max_jit_classex_standard_wrap() function. The
max_jit_classex_standard_wrap() function will add all typed methods defined in the Jitter class, as well getter and setter
methods for attributes that are not opaque (i.e. private), and all the methods that are common to Jitter objects like
getattributes, getstate, summary, importattrs, exportattrs, etc.
Now that you have wrapped the Jitter class, you can add any additional methods that you wish, such as your
inlet/outlet assistance method, or something specific to the Max object. Like Jitter objects, you can also add
methods which have defer or usurp wrappers, and these should be added via the max_addmethod_defer_low() or
max_addmethod_usurp_low() functions, rather than simply using the traditional addmess() function. C
21.4 Constructor
Inside the Max object constructor, there are a few things which are different than building an ordinary Max external. If
your object is to respond to attribute arguments, the constructor must be defined to take variable number of typed atom
arguments, accomplished with the A_GIMME signature. You allocate your Max object with the max_jit_obex_new() func-
tion, instead of the traditional newobject function. You need to pass your Jitter class name to the max_jit_obex_new()
function, which also allocates and initializes your obex data. If successful, you should proceed to add your general pur-
pose "dumpout" outlet, used for returning attribute queries and other methods that provide information like ∗jit.movie∗'s
framedump method's frame number or read method success code, with the max_jit_object_dumpout_set() function. If
your object is a Matrix Operator that calls max_jit_mop_setup_simple() you will not need to explicitly call max_jit_←-
object_dumpout_set(), as max_jit_mop_setup_simple() calls max_jit_object_dumpout_set() internally.
You then allocate your Jitter object with jit_object_new(), and store it in your obex data via max_jit_obex_jitob_set().
Note that this Jitter object instance can always be found with the function max_jit_obex_jitob_get(). If you wish, prior
to allocating your Jitter object, you can look at your non-attribute arguments first — those arguments up to the location
returned by max_jit_attr_args_offset() — and make use of them in your Jitter object constructor. It is typical to process
attribute arguments after you've allocated both the Max and Jitter object instances, with max_jit_attr_args(), which is
passed the Max object instance. If you wanted to use the attribute arguments somehow in your Jitter object constructor,
you would need to parse the attribute arguments yourself. If you are not able to allocate your Jitter object (as is the
case if you have run out of memory or if Jitter is present but not authorized), it is important that you clean up your Max
wrapper object, and return NULL.
21.5 Destructor
In your Max object destructor, you additionally need to free your internal Jitter object with jit_object_free(), and free
any additional obex data with max_jit_obex_free(). Matrix operators will typically require that max_jit_mop_free() is
called, to free the resources allocated for matrix inputs and outputs. If your object has attached to a registered object
for notification via jit_object_attach(), you should detach from that object in your destructor using jit_object_detach() to
prevent invalid memory accesses as the registered object might attempt to notify the memory of a now freed object.
Object registration and notification is discussed in further detail in following chapters.
21.6 Dumpout
The general purpose outlet, also known as "dumpout", is automatically used by the Max wrapper object when calling
attribute getters and several of the standard methods like summary, or getattributes. It is also available for use in any
other Max method you want, most easily accessed with the max_jit_obex_dumpout() function that operates similar to
outlet_anything(), but uses the max object pointer rather than the outlet pointer as the first argument. The outlet pointer
which has been set in your constructor can be queried with the max_jit_obex_dumpout_get() function, and used in the
standard outlet calls. However, it is recommended for routing purposes that any output through the dumpout outlet is a
message beginning with a symbol, rather than simply a bang, int, or float. Therefore, outlet_anything() makes the most
sense to use.
Cycling ’74
86 Jitter Max Wrappers
To add additional inlets and outlets to your Max external, a few things should be noted. First, if your object is a Matrix
Operator, matrix inlets and outlets will be added either through either the high level max_jit_mop_setup_simple(), or
lower level max_jit_mop_inputs() or max_jit_mop_outputs() calls. These Matrix Operator functions will be covered in the
chapter on Matrix Operators. Secondly, if your object is an MSP object, all signal inlets and outlets must be leftmost,
and all non-signal inlets and outlets must be to the right of any single inlets or outlets—i.e. they cannot be intermixed.
Lastly, additional inlets should use proxies (covered in detail in the Max developer documentation) so that your object
knows which inlet a message has been received. This is accomplished with the max_jit_obex_proxy_new() function.
The inlet number is zero based, and you do not need to create a proxy for the leftmost inlet. Inside any methods which
need to know which inlet the triggering message has been received, you can use the max_jit_obex_inletnumber_get()
function.
Sometimes you will need additional attributes which are specific to the Max wrapper class, but are not part of the
internal Jitter class. Attributes objects for the Max wrapper class are defined in the same way as those for the Jit-
ter class, documented in the previous chapter. However, these attributes are not added to the Max class with the
jit_class_addattr() function, but instead with the max_jit_classex_addattr() function, which takes the classex pointer re-
turned from max_jit_classex_setup(). Attribute flags, and custom getter and setter methods should be defined exactly
as they would for the Jitter class.
Cycling ’74
Chapter 22
The purpose of this chapter is to give a quick and high level overview of how to develop a simple Matrix Operator (MOP),
which can process the matrix type most commonly used for video streams—i.e.
4 plane char data. For this task, we will use the jit.scalebias SDK example. More details such as how to make a Matrix
Operator which deals with multiple types, plane count, dimensionality, inputs, outputs, etc. will appear in the following
chapter. This chapter assumes familiarity with Jitter's multi-dimensional matrix representation and Matrix Operators
used from the Max patcher, as discussed in the Jitter Tutorial, and as well as the preceding chapters on the Jitter object
model and Max wrapper classes.
In the Jitter class definition, we introduce a few new concepts for Matrix Operators. In addition to the standard method
and attribute definitions discussed in the Jitter object model chapter, you will want to define things like how many inputs
and outputs the operator has, and what type, plane count, and dimension restrictions the operator has. These are
accomplished by creating an instance of the jit_mop class, setting some state for the jit_mop object and adding this
object as an adornment to your Jitter class. The following code segment references the jit.scalebias SDK example.
// create a new instance of jit_mop with 1 input, and 1 output
mop = jit_object_new(_jit_sym_jit_mop,1,1);
// enforce a single type for all inputs and outputs
jit_mop_single_type(mop,_jit_sym_char);
// enforce a single plane count for all inputs and outputs
jit_mop_single_planecount(mop,4);
// add the jit_mop object as an adornment to the class
jit_class_addadornment(_jit_scalebias_class,mop);
You create your jit_mop instance in a similar fashion to creating your attribute instances, using jit_object_new(). The
jit_mop constructor has two integer arguments for inputs and outputs, respectively. By default, each MOP input and
output is unrestricted in plane count, type, and dimension, and also are linked to the plane count, type, and dimen-
sions of the first (i.e. leftmost) input. This default behavior can be overridden, and this simple 4 plane, char type,
jit.scalebias example enforces the corresponding type and plane count restrictions via the jit_mop_single_type() and
jit_mop_single_planecount() utility functions. For more information on the jit_mop class, please see the following chap-
ter on MOP details and the Jitter API reference.
Once you have created your jit_mop instance, and configured it according to the needs of your object, you add it as an
adornment to your Jitter class with the jit_class_add_adornment() function. Adornments are one way for Jitter objects
to have additional information, and in some instances behavior, tacked onto an existing class. Adornments will be
discussed in detail in a later chapter.
You also want to define your matrix calculation method, where most of the work of a Matrix Operator occurs, with the
jit_class_addmethod() function as a private, untyped method bound to the symbol matrix_calc.
jit_class_addmethod(_jit_scalebias_class,
(method)jit_scalebias_matrix_calc,
"matrix_calc", A_CANT, 0L);
Cycling ’74
88 Matrix Operator QuickStart
You don't need to add anything special to your Matrix Operator's constructor or destructor, aside from the standard
initialization and cleanup any Jitter object would need to do. Any internal matrices for input and outputs are maintained,
and only required, by the Max wrapper's asynchronous interface. The Jitter MOP contains no matrices for inputs and
outputs, but rather expects that the matrix calculation method is called with all inputs and outputs synchronously. When
used from languages like C, Java, and JavaScript, it is up to the programmer to maintain and provide any matrices which
are being passed into the matrix calculation method.
The most important method for Matrix Operators, and the one in which the most work typically occurs is in the matrix
calculation, or "matrix_calc" method, which should be defined as a private, untyped method with the A_CANT type
signature, and bound to the symbol "matrix_calc". In this method your object receives a list of input matrices and output
matrices to use in its calculation. You need to lock access to these matrices, inquire about important attributes, and
ensure that any requirements with respect to type, plane count, or dimensionality for the inputs are met before actually
processing the data, unlocking access to the matrices and returning. It should be defined as in the following example.
t_jit_err jit_scalebias_matrix_calc(t_jit_scalebias *x,
void *inputs, void *outputs)
{
t_jit_err err=JIT_ERR_NONE;
long in_savelock,out_savelock;
t_jit_matrix_info in_minfo,out_minfo;
char *in_bp,*out_bp;
long i,dimcount,planecount,dim[JIT_MATRIX_MAX_DIMCOUNT];
void *in_matrix,*out_matrix;
// get the zeroth index input and output from
// the corresponding input and output lists
in_matrix = jit_object_method(inputs,_jit_sym_getindex,0);
out_matrix = jit_object_method(outputs,_jit_sym_getindex,0);
// if the object and both input and output matrices
// are valid, then process, else return an error
if (x&&in_matrix&&out_matrix)
{
// lock input and output matrices
in_savelock =
(long) jit_object_method(in_matrix,_jit_sym_lock,1);
out_savelock =
(long) jit_object_method(out_matrix,_jit_sym_lock,1);
// fill out matrix info structs for input and output
jit_object_method(in_matrix,_jit_sym_getinfo,&in_minfo);
jit_object_method(out_matrix,_jit_sym_getinfo,&out_minfo);
// get matrix data pointers
jit_object_method(in_matrix,_jit_sym_getdata,&in_bp);
jit_object_method(out_matrix,_jit_sym_getdata,&out_bp);
// if data pointers are invalid, set error, and cleanup
if (!in_bp) { err=JIT_ERR_INVALID_INPUT; goto out;}
if (!out_bp) { err=JIT_ERR_INVALID_OUTPUT; goto out;}
// enforce compatible types
if ((in_minfo.type!=_jit_sym_char) ||
(in_minfo.type!=out_minfo.type))
{
err=JIT_ERR_MISMATCH_TYPE;
goto out;
}
// enforce compatible planecount
if ((in_minfo.planecount!=4) ||
(out_minfo.planecount!=4))
{
err=JIT_ERR_MISMATCH_PLANE;
goto out;
}
// get dimensions/planecount
dimcount = out_minfo.dimcount;
planecount = out_minfo.planecount;
for (i=0;i<dimcount;i++)
{
// if input and output are not matched in
Cycling ’74
22.4 Processing N-Dimensional Matrices 89
Since Jitter supports the processing of N-dimensional matrices where N can be any number from 1 to 32, most Matrix
Operators are designed with a recursive function that will process the data in some lower dimensional slice, most often
2 dimensional. The recursive function that does this is typically named myobject_calculate_ndim(), and is called by your
matrix_calc method either directly or via one of the parallel processing utility functions, which are discussed in a future
chapter.
It is out of the scope of this documentation to provide a detailed tutorial on fixed point or pointer arithmetic, both of
which are used in this example. The code increments a pointer through the matrix data, scaling each planar element
of each matrix cell by some factor and adding some bias amount. This is done with fixed point arithmetic (assuming an
8bit fractional component), since a conversion from integer to floating point data and back is an expensive operation.
The jit.scalebias object also has two modes, one which sums the planes together, and one which processes each plane
independently. You can improve performance by case handling on a per row, rather than per cell basis, and reduce your
code somewhat by case handling on a per row, rather than per matrix basis. While a slight performance increase could
be made by handling on a per matrix basis, per row is usually a decent point at which to make such an optimization
trade off.
// recursive function to handle higher dimension matrices,
// by processing 2D sections at a time
void jit_scalebias_calculate_ndim(t_jit_scalebias *x,
long dimcount, long *dim, long planecount,
t_jit_matrix_info *in_minfo, char *bip,
t_jit_matrix_info *out_minfo, char *bop)
{
long i,j,width,height;
uchar *ip,*op;
long ascale,rscale,gscale,bscale;
long abias,rbias,gbias,bbias,sumbias;
long tmp;
if (dimcount<1) return; //safety
switch(dimcount)
{
case 1:
// if only 1D, interpret as 2D, falling through to 2D case
dim[1]=1;
case 2:
// convert floating point scale factors to a fixed point int
ascale = x->ascale*256.;
rscale = x->rscale*256.;
gscale = x->gscale*256.;
bscale = x->bscale*256.;
// convert floating point bias values to a fixed point int
abias = x->abias*256.;
rbias = x->rbias*256.;
gbias = x->gbias*256.;
bbias = x->bbias*256.;
// for efficiency in sum mode (1), make a single bias value
Cycling ’74
90 Matrix Operator QuickStart
sumbias = (x->abias+x->rbias+x->gbias+x->bbias)*256.;
width = dim[0];
height = dim[1];
// for each row
for (i=0;i<height;i++)
{
// increment data pointers according to byte stride
ip = bip + i*in_minfo->dimstride[1];
op = bop + i*out_minfo->dimstride[1];
switch (x->mode) {
case 1:
// sum together, clamping to the range 0-255
// and set all output planes
for (j=0;j<width;j++) {
tmp = (long)(*ip++)*ascale;
tmp += (long)(*ip++)*rscale;
tmp += (long)(*ip++)*gscale;
tmp += (long)(*ip++)*bscale;
tmp = (tmp»8L) + sumbias;
tmp = (tmp>255)?255:((tmp<0)?0:tmp);
*op++ = tmp;
*op++ = tmp;
*op++ = tmp;
*op++ = tmp;
}
break;
default:
// apply to each plane individually
// clamping to the range 0-255
for (j=0;j<width;j++) {
tmp = (((long)(*ip++)*ascale)»8L)+abias;
*op++ = (tmp>255)?255:((tmp<0)?0:tmp);
tmp = (((long)(*ip++)*rscale)»8L)+rbias;
*op++ = (tmp>255)?255:((tmp<0)?0:tmp);
tmp = (((long)(*ip++)*gscale)»8L)+gbias;
*op++ = (tmp>255)?255:((tmp<0)?0:tmp);
tmp = (((long)(*ip++)*bscale)»8L)+bbias;
*op++ = (tmp>255)?255:((tmp<0)?0:tmp);
}
break;
}
}
break;
default:
// if processing higher dimension than 2D,
// for each lower dimensioned slice, set
// base pointer and recursively call this function
// with decremented dimcount and new base pointers
for (i=0;i<dim[dimcount-1];i++)
{
ip = bip + i*in_minfo->dimstride[dimcount-1];
op = bop + i*out_minfo->dimstride[dimcount-1];
jit_scalebias_calculate_ndim(x,dimcount1,
dim,planecount,in_minfo,ip,out_minfo,op);
}
}
}
Rather than using multidimensional arrays, Jitter matrix data is packed in a single dimensional array, with defined byte
strides for each dimension for greatest flexibility. This permits matrices to reference subregions of larger matrices, as
well as support data that is not tightly packed. Therefore, rather than using multidimensional array syntax, this code
uses pointer arithmetic to access each plane of each cell of the matrix, adding the corresponding byte strides to the
base pointer for each dimension across which it is iterating. These byte strides are stored in the dimstride entry of the
t_jit_matrix_info struct. Note that Jitter requires that planes within a cell, and cells across the first dimension (dim[0]) are
tightly packed. The above code assumes that this is the case, using a simple pointer increment for each plane and cell,
rather than looking up byte strides for dim[0].
In order to use the MOP class in a Max patcher you need to make a Max wrapper class. In addition to the standard
methods used to wrap any Jitter class, MOPs need to add special methods and information to the Max class. One of
Cycling ’74
22.6 The Max Class Constructor/Destructor 91
the things that needs to happen is that the Max wrapper class needs to allocate and maintain instances of jit.matrix
for each matrix input and output other than the leftmost input, to accommodate Max's asynchronous event model. In
order to perform this maintenance, the Max wrapper class must have special methods and attributes for setting the
type, plane count, dimensions, adaptability, and named references for the internal matrices. All of these messages
are exclusive to the Max wrapper implementation, and are not used by the C, Java, or JavaScript usage of Matrix
Operators. There are also common methods and attributes for the matrix output mode, and the jit_matrix and bang
messages, all of which are specific to the MOP's Max wrapper. These special attributes and methods are added
by the max_jit_classex_mop_wrap() function, which should be called inside your Max external's ext_main() function,
after calling max_jit_classex_setup() and jit_class_findbyname(), and before calling max_jit_classex_standard_wrap().
Several default methods and attributes can be overridden using the various flags that can be combined for the flags
argument to max_jit_classex_mop_wrap(). These flags, which for most simple MOPs won't be necessary, are listed
below.
#define MAX_JIT_MOP_FLAGS_OWN_ALL 0xFFFFFFFF
#define MAX_JIT_MOP_FLAGS_OWN_JIT_MATRIX 0x00000001
#define MAX_JIT_MOP_FLAGS_OWN_BANG 0x00000002
#define MAX_JIT_MOP_FLAGS_OWN_OUTPUTMATRIX 0x00000004
#define MAX_JIT_MOP_FLAGS_OWN_NAME 0x00000008
#define MAX_JIT_MOP_FLAGS_OWN_TYPE 0x00000010
#define MAX_JIT_MOP_FLAGS_OWN_DIM 0x00000020
#define MAX_JIT_MOP_FLAGS_OWN_PLANECOUNT 0x00000040
#define MAX_JIT_MOP_FLAGS_OWN_CLEAR 0x00000080
#define MAX_JIT_MOP_FLAGS_OWN_NOTIFY 0x00000100
#define MAX_JIT_MOP_FLAGS_OWN_ADAPT 0x00000200
#define MAX_JIT_MOP_FLAGS_OWN_OUTPUTMODE 0x00000400
Inside your Max class' constructor you need to allocate the matrices necessary for the MOP inputs and outputs, the cor-
responding matrix inlets and outlets, process matrix arguments and other MOP setup. The max_jit_mop_setup_simple()
function takes care of these functions and some of the other necessary tasks of wrapping your Jitter instance. As such,
the use of this function simplifies your Jitter class wrapping even further for the simple case where no special behavior,
incompatible with max_jit_mop_setup_simple() is required. Here is the constructor for the Max class of the jit.scalebias
object.
void *max_jit_scalebias_new(t_symbol *s, long argc, t_atom *argv)
{
t_max_jit_scalebias *x;
void *o;
if (x = (t_max_jit_scalebias *)
max_jit_obex_new(
max_jit_scalebias_class,
gensym("jit_scalebias")))
{
// instantiate Jitter object
if (o=jit_object_new(gensym("jit_scalebias")))
{
// handle standard MOP max wrapper setup tasks
max_jit_mop_setup_simple(x,o,argc,argv);
// process attribute arguments
max_jit_attr_args(x,argc,argv);
}
else
{
error("jit.scalebias: could not allocate object");
freeobject(x);
}
}
return (x);
}
Below is the listing of the max_jit_mop_setup_simple() function, demonstrating the smaller pieces, it manages for you.
If your object has special requirements, you can use whatever subset of the following function as necessary.
t_jit_err max_jit_mop_setup_simple(void *x, void *o, long argc, t_atom *argv)
{
max_jit_obex_jitob_set(x,o);
max_jit_obex_dumpout_set(x,outlet_new(x,NULL));
Cycling ’74
92 Matrix Operator QuickStart
max_jit_mop_setup(x);
max_jit_mop_inputs(x);
max_jit_mop_outputs(x);
max_jit_mop_matrix_args(x,argc,argv);
return JIT_ERR_NONE;
}
In your Max class' destructor, you need to free the resources allocated for your MOP. This is accomplished with the
max_jit_mop_free() function, which should be called before you free your internal Jitter instance, and your Max class'
obex data. As an example, the jit.scalebias destructor is listed below.
void max_jit_scalebias_free(t_max_jit_scalebias *x)
{
// free MOP max wrapper resources
max_jit_mop_free(x);
// lookup internal Jitter object instance and free
jit_object_free(max_jit_obex_jitob_get(x));
// free resources associated with obex entry
max_jit_obex_free(x);
}
Cycling ’74
Chapter 23
The purpose of this chapter is to fill in the details of what a Matrix Operator is and how it works.
Matrix data in Jitter is typically considered raw data without respect to what the data represents. This permits simple
fundamental operations to be applied to different sorts of data without needing to know any special information. For this
reason most MOPs are general purpose. The jit.scalebias example from the preceding chapter could be considered
video specific in its terminology, and type and plane count restrictions, but fundamentally it is just calculating a product
and sum on each plane of an incoming matrix. In this chapter, we'll cover the details of how to configure MOP inputs and
outputs, any attribute restrictions or linking for those inputs and outputs, what you must do in your matrix_calc method
and how you expose your MOP to the Max environment, overriding default behavior if necessary.
As discussed in the Matrix Operator Quick Start, for MOPs you must create an instance of jit_mop with the jit_object←-
_new() function and add it to your Jitter class as an adornment with the jit_class_addadornment() function. The jit_mop
object holds information such as how many inputs and outputs the object has, what types, plane count, and dimension
counts are supported, and how inputs should respond to incoming matrices. This information is only relevant to wrappers
of the Jitter object which actually maintain additional matrices for inputs and outputs, as is the case with the MOP Max
wrapper class. When used from C, Java, or JavaScript, it is the the programmer's responsibility to pass in matrices that
conform to any restrictions imposed by the MOP. An example of instantiating and adding the jit_mop object is below.
// create a new instance of jit_mop with 1 input, and 1 output
mop = jit_object_new(_jit_sym_jit_mop,1,1);
// add jit_mop object as an adornment to the class
jit_class_addadornment(_jit_your_class,mop);
Each instance of jit_mop contains some number of inputs and outputs, specified by the input and output arguments
to the constructor. For each of these inputs and outputs there is an instance of jit_mop_io which records information
specific to that input or output, such as type, plane count, and dimension restrictions. You can access the input or output
objects by calling the getinput or getoutput methods with an integer index argument as below:
input = jit_object_method(mop,_jit_sym_getinput,1);
output = jit_object_method(mop,_jit_sym_getoutput,1);
Cycling ’74
94 Matrix Operator Details
Once you have obtained references to these inputs or outputs, you may query or set the jit_mop_io attributes. The
attributes typically configured are: types, which is a list of symbols of permitted types, the first of which being the
default; mindim and maxdim, which are the minimum and maximum permitted sizes for each dimension; mindimcount
and maxdimcount, which are the minimum and maximum permitted number of dimensions permitted; minplanecount
and maxplanecount, which are the minimum and maximum number of planes permitted; typelink, which is the flag that
determines if the I/O should change its type to whatever the leftmost incoming matrix is; dimlink, which is the flag that
determines if the I/O should change its dimensions to whatever the leftmost incoming matrix is; and planelink, which is
the flag that determines if the I/O should change its plane count to whatever the leftmost incoming matrix is.
By default, all types, dimensions and plane count are permitted, and all linking is enabled. If you wish your MOP
to have some specific restrictions, or difference in linking behaviors for any input or output in particular, you can set
the corresponding attributes. For example, to set the plane count to always be four planes, you would set both the
minplanecount and maxplanecount attributes to 4, as below:
output = jit_object_method(mop,_jit_sym_getoutput,1);
jit_attr_setlong(output,_jit_sym_minplanecount,4);
jit_attr_setlong(output,_jit_sym_maxplanecount,4);
The jit.scalebias example could have set the planecount using the minplanecount and maxplanecount attributes rather
than calling the utility function jit_mop_single_planecount(), which internally sets these attributes. A similar thing could
be done to restrict type and dimensions. As for linking, if you wish to develop an object where the right hand input does
not adapt to the size of the leftmost input, as is the case with jit.convolve, you would turn off the dimlink attribute, as
below:
input2 = jit_object_method(mop,_jit_sym_getinput,2);
jit_attr_setlong(input2,_jit_sym_dimlink,0);
Similar could be done to remove type and planecount linking, and the utility functions jit_mop_input_nolink() and
jit_mop_output_nolink() set all of these link attributes to false (zero).
For right hand matrix inputs, incoming data is typically copied by the MOP Max wrapper class. When an incom-
ing matrix is received by the MOP Max wrapper class, a function called the ioproc is called, and the default ioproc
copies the data, using the current input attributes (which might be linked to the lefthand input). The default io-
proc can be overridden by calling the ioproc method followed by a function with the signature as listed below in the
jit_mop_ioproc_copy_adapt() function. The jit_mop_ioproc_copy_adapt() function will always adapt to that inlet's incom-
ing matrix attributes, as long as they don't conflict with any restrictions. The SDK project for jit.concat demonstrates the
use of the jit_mop_ioproc_copy_adapt() function.
t_jit_err jit_mop_ioproc_copy_adapt(void *mop, void *mop_io, void *matrix)
{
void *m; // destination matrix
t_jit_matrix_info info;
// look up destination matrix from mop_io
if (matrix&&(m=jit_object_method(mop_io,_jit_sym_getmatrix)))
{
// retrieve incoming matrix info
jit_object_method(matrix,_jit_sym_getinfo,&info);
//restrict matrix info based on mop_io attribtues
jit_object_method(mop_io,_jit_sym_restrict_type,&info);
jit_object_method(mop_io,_jit_sym_restrict_dim,&info);
jit_object_method(mop_io,_jit_sym_restrict_planecount,&info);
// set destination matrix info
jit_object_method(m,_jit_sym_setinfo,&info);
// copy the data with the frommatrix method
jit_object_method(m,_jit_sym_frommatrix,matrix,NULL);
}
return JIT_ERR_NONE;
}
Cycling ’74
23.5 Variable Inputs/Outputs 95
You can specify variable input/output MOPs with a negative argument for input and/or outputs when constructing your
jit_mop object. When the using variable inputs and/or outputs, there is not a jit_mop_io for each input and/or output
within your class definition, and therefore the template type, dim, planecount, and linking attributes are not settable.
If anything but the default behavior is required, you must accomplished it in another way — for example, either by
overriding the jit_matrix method of the MOP Max wrapper class, or defining an mproc method to be called from within
the standard jit_matrix method of the MOP Max wrapper class. The jit.pack, jit.unpack, jit.scissors, and jit.glue objects
are a few SDK examples of MOPs with variable inputs and outputs. More information on overriding the jit_matrix, mproc,
and other default methods of the MOP Max wrapper class is covered later in this chapter.
Once you have configured all of the inputs and outputs of your jit_mop object, you must add your jit_mop object to
your Jitter class with the jit_class_addadornment() function. Adorments can be queried from the Jitter class at any
time by calling jit_class_adornment_get() with the Jitter class pointer and the class name of the adornment object, as
demonstrated below.
// add jit_mop object as an adornment to the class
jit_class_addadornment(_jit_your_class,mop);
// look up jit_mop adornment
mop = jit_class_adornment_get(_jit_your_class,_jit_sym_jit_mop);
The entry point of the MOP Jitter class is the matrix_calc method, which is passed a list of matrices for the input, and a
list of matrices for the output. It is not the responsibility of the matrix_calc method to perform any copying and adaptation
behavior, but rather simply ensure that the matrices are valid, compatible, and if so, process. Certain objects may modify
the dim, type, or planecount of the output matrices — e.g. the SDK project, jit.thin. However, it is the calling party's
responsibility to perform any copying and conformance to MOP I/O restrictions as defined by the jit_mop_io objects—i.e.
either the Max wrapper class, or the C, Java, or Javascript code which calls the matrix_calc method.
The input and output lists passed as arguments to your matrix_calc method are Jitter objects, and pointers to the
individual inputs and outputs are acquired by calling the getindex method with an integer argument specifying the zero
based list index. The return values should be tested to make sure they are not null. For example:
// get the zeroth index input and output from
// the corresponding input and output lists
in_matrix = jit_object_method(inputs,_jit_sym_getindex,0);
out_matrix = jit_object_method(outputs,_jit_sym_getindex,0);
// if the object and both input and output matrices
// are valid, then process, else return an error
if (x&&in_matrix&&out_matrix)
{
// ... process data ...
} else {
return JIT_ERR_INVALID_PTR;
}
Technically, you can also pass in an instance of jit_matrix in place of a list for the input or output arguments, since
jit_matrix has a getindex method which returns the jit_matrix instance. This is an example of dynamic binding at work.
Cycling ’74
96 Matrix Operator Details
Another example of dynamic binding inside the matrix_calc method is that the list elements might be instances of jit←-
_mop_io, rather than instances of jit_matrix. However, since Jitter uses dynamic binding and the jit_mop_io object is a
"decorator" class for jit_matrix, all corresponding methods are passed on to the jit_matrix referenced by the jit_mop_io.
In fact, any Jitter objects which respond to the standard interface for jit_matrix could be passed as inputs or outputs.
If this seems confusing, you need not think about the underlying implementation further, but instead can assume that
what is being passed in is simply an instance of jit_matrix. After all it should behave like one, even if it is not.
Prior to working with a matrix, it is necessary to "lock" it so that the data and attributes will not be changed across the
duration of the operation. This is accomplished by calling the jit_matrix instance's lock method with an integer argument
of 1 (true) to lock the matrix. You should store the current lock state to restore when you're done processing. The lock
operation should be the first thing to do after ensuring that the matrix objects are not NULL. For example
// lock input and output matrices
in_savelock = (long) jit_object_method(in_matrix,_jit_sym_lock,1);
out_savelock = (long) jit_object_method(out_matrix,_jit_sym_lock,1);
// ... process data ...
out:
// restore matrix lock state to previous value
jit_object_method(out_matrix,_jit_sym_lock,out_savelock);
jit_object_method(in_matrix,_jit_sym_lock,in_savelock);
Once you have locked the matrices, you are ready to find out some information about them. This is accomplished
by calling the getinfo method with a pointer to an instance of the t_jit_matrix_info struct. The t_jit_matrix_info struct
contains several common attributes of the matrix and data organization of the matrix data, and is a useful way to obtain
this information in one call, rather than querying each attribute individually. This information is typically tested to verify
compatibility with any assumptions the matrix_calc method needs to make (since this method might be called from C,
Java, or Javascript, you cannot assume that the MOP Max wrapper will have enforced these assumptions). It is also
used to perform the appropriate pointer arithmetic based on type, plane count, dimensions, and the byte stride of those
dimensions, since higher dimensions may not be tightly packed. The t_jit_matrix_info struct is listed below:
typedef struct _jit_matrix_info
{
long size; // in bytes (0xFFFFFFFF=UNKNOWN)
t_symbol *type; // primitive type
long flags; // matrix flags: my data?, handle?
long dimcount; // # of dimensions
long dim[JIT_MATRIX_MAX_DIMCOUNT]; // dimension sizes
long dimstride[JIT_MATRIX_MAX_DIMCOUNT]; // in bytes
long planecount; // # of planes
} t_jit_matrix_info;
And here is an example of calling the getinfo method to fill out the t_jit_matrix_info struct:
// fill out matrix info structs for input and output
jit_object_method(in_matrix,_jit_sym_getinfo,&in_minfo);
jit_object_method(out_matrix,_jit_sym_getinfo,&out_minfo);
The t_jit_matrix_info struct is the meta data, but the actual matrix data can be accessed by acquiring the data pointer.
You accomplish this by calling the matrix's getdata method, passing in a pointer to a pointer. This pointer can be any
type, but it is typically a char (or byte) pointer since you may need to perform bytewise pointer arithmetic depending on
Cycling ’74
23.12 Processing the Data 97
the type and dimstride of your matrix. It is essential to verify that this pointer is valid before attempting to operate on the
data, as demonstrated below.
// get matrix data pointers
jit_object_method(in_matrix,_jit_sym_getdata,&in_bp);
jit_object_method(out_matrix,_jit_sym_getdata,&out_bp);
// if data pointers are invalid, set error, and cleanup
if (!in_bp) { err=JIT_ERR_INVALID_INPUT; goto out;}
if (!out_bp) { err=JIT_ERR_INVALID_OUTPUT; goto out;}
While it is possible to incorporate the data processing code inside the matrix_calc method, it is typical to rely on other
routines to accomplish the N dimensional processing through recursion, potentially dispatching to multiple processors.
The N-dimensional recursive processing function (typically named myobject_calculate_ndim) is discussed in the next
section. You should pass in to the calculate_ndim function your object pointer, the overall dimension count, dimension
sizes, planecount to consider in your calculation, together with the necessary matrix info structs and data pointers for
each input and output. You can call this method directly as is the case in the following code:
// call calculate_ndim function directly in current thread
jit_scalebias_calculate_ndim(x, dimcount, dim, planecount,
&in_minfo, in_bp, &out_minfo, out_bp);
Or you can call this method with the parallel processing utility functions provided with Jitter 1.5 to automatically dispatch
the processing of large matrices across multiple processors when available. This figure illustrates the dispatching and
calculating of the parallel processing utility:
The parallel processing is accomplished by breaking up the matrix into smaller matrices that each reference subregions
of the original inputs and outputs. No new objects are created, but rather just additional t_jit_matrix_info structs and
offset data pointers. Jitter 1.5 maintains a pool of worker threads for this purpose, so there is no thread creation
overhead, but rather only some small thread synchronization overhead. Jitter 1.5 only dispatches across multiple threads
when the data count is large enough to justify this thread synchronization overhead.
An important thing worth noting is that if your object performs some kind of spatial operation (e.g. convolution, rotation,
scaling, etc.), you will either need to account for the matrix segmentation used by the parallel utilities or avoid using
parallel processing and call directly in the current thread. Since the jit.scalebias example only processes one pixel at a
time (i.e. a pointwise operation), it is inherently parallelizable, so it takes advantage of multiple processors as below:
// calculate, using the parallel utility function to
// call the calculate_ndim function in multiple
// threads if there are multiple processors available
jit_parallel_ndim_simplecalc2(
(method)jit_scalebias_calculate_ndim,
x, dimcount, dim, planecount,
&in_minfo, in_bp, &out_minfo, out_bp,
0, 0 );
Important Note: If you aren't sure if your object is a pointwise operator, or don't fully understand how to make your
algorithm parallelizable, you shouldn't use the parallel utility functions in your object. You should simply call the function
directly.
In the Matrix Operator Quick Start chapter, we discussed how to define a recursive function to process N-dimensional
data in 2D slices, using the jit.scalebias object as an example. This example was restricted to processing four plane
char data, but many Jitter objects work with any type of data and any plane count. In order to support all types and plane
counts, there needs to be some case handling to know how to step through the data, and what type data to interpret as
so that you can perform the appropriate operations. There are a number of ways to approach this logic, and decisions to
Cycling ’74
98 Matrix Operator Details
make with respect to optimization. All this case handling can be a bit cumbersome, so when initially developing objects,
it probably makes sense for you to focus on a single type and plane count, and only after you've adequately defined your
operation, attempt to make your code robust to process any type of data and consider optimization of certain cases. The
use of C macros, or C++ templates might be useful things to explore for better code re-use. As for code optimization,
typically a decent atomic element to try and optimize is the "innermost" loop, avoiding branch conditions where possible.
This function is at the heart of the logic you will add in your own custom object. Since there is no "right way" to process
this data, we won't cover any more code listings for the recursive N-dimensional processing function. However, the SDK
projects that are good examples include: jit.clip, which performs a planar independent, pointwise operation (limiting
numbers to some specified range); jit.rgb2luma, which performs a planar dependent, pointwise operation (converting
RGB color to luminance); and jit.transpose, which performs a planar independent, spatial operation (rows become
columns). For more ideas about N-dimensional matrix processing, we would recommend reading one of the several
books available on 2D signal processing and/or image processing. Most of these concepts are easily generalized to
higher dimensions.
MOP Max wrapper classes typically have a large amount of default behavior, as setup through the max_jit_classex_←-
mop_wrap function, based on the jit_mop Jitter class adornment, and user specified flags. You can either override all
of the default behavior or just specific features. If you wish to override all of the default behavior, you can use the flag
MAX_JIT_MOP_FLAGS_OWN_ALL, when calling the max_jit_classex_mop_wrap() function. If you need to make use
of the jit_mop adornment(), the jit_mop can be looked up by calling the jit_class_adornment_get() method on the Jitter
class. The jit_mop_io inputs and outputs can be queried and their attributes inspected, similar to how they were set
in the MOP Jitter class definition, described earlier in this chapter. Here is an example of how to look up the jit_mop
adornment of the jit.scalebias object:
// look up jitter class by name
jclass = jit_class_findbyname(gensym("jit_scalebias"));
// look up jit_mop adornment
mop = jit_class_adornment_get(jclass,_jit_sym_jit_mop);
By default, a jit_matrix method is added which automatically manages matrix copying and calculation based on the
incoming data. Most typical MOPs simply use the default jit_matrix method. However there are instances where it is
necessary to override the default MOP method to get special behavior, such as recording which matrix input data is
being input to as is the case for the jit.op SDK example, or to do something other than standard copying and adap-
tation as is the case for the jit.pack or jit.str.op SDK examples, or to prevent any jit_matrix method at all, as is the
case for the jit.noise SDK example. To prevent the default jit_matrix method from being defined, you can use the flag
MAX_JIT_MOP_FLAGS_OWN_JIT_MATRIX, when calling the max_jit_classex_mop_wrap() function. To define your
own jit_matrix method, you can add an A_GIMME method bound to the symbol jit_matrix, in your ext_main() function.
Here's an example from jit.op:
// add custom jit_matrix method in ext_main()
addmess((method)max_jit_op_jit_matrix, "jit_matrix", A_GIMME, 0);
void max_jit_op_jit_matrix(t_max_jit_op *x, t_symbol *s, short argc,
t_atom *argv)
{
if (max_jit_obex_inletnumber_get(x))
{
// if matrix is received in right input,
// record to override float or int input
x->last = OP_LAST_MATRIX;
}
// now pass on to the default jit_matrix method
max_jit_mop_jit_matrix(x,s,argc,argv);
}
The jit.pack and jit.str.op examples are a bit more involved and also better illustrate the kinds of tasks the default jit_←-
matrix method performs.
Cycling ’74
23.16 Overriding the bang and outputmatrix Methods 99
A MOP Max wrapper class typically has a bang and outputmatrix method. These two methods are typically equiv-
alent, and by default, both send out the most recently calcuated matrix output. Certain objects that don't have a
matrix output, like the jit.3m SDK example, typcially override these messages with their own bang and sometimes
outputmatrix method. These methods can be overridden by using the MAX_JIT_MOP_FLAGS_OWN_BANG and
MAX_JIT_MOP_FLAGS_OWN_OUTPUTMATRIX flags when calling the max_jit_classex_mop_wrap() function. These
flags are typically both passed in together.
For each input and output, other than the leftmost input, there is, by default, an attribute added to query and set
that input or output's matrix attributes, including name, type, dim, and planecount. While overriding the default at-
tribute behavior is conceivably necessary to perform very specialized behavior, it is not used by any of the SDK
examples. To prevent the addition of the default attributes for name, type, dim, and planecount, you can use the
MAX_JIT_MOP_FLAGS_OWN_NAME, MAX_JIT_MOP_FLAGS_OWN_TYPE, MAX_JIT_MOP_FLAGS_OWN_DIM,
and MAX_JIT_MOP_FLAGS_OWN_PLANECOUNT flags when calling the max_jit_classex_mop_wrap() function. To
define your own attributes, you would follow the same means of defining any attributes for a Max wrapper class with the
appropriate attribute name you wish to override.
By default, a clear and a notify method are added. The default clear method clears each of the input and output matrices.
The default notify method, max_jit_mop_notify(), is called whenever any of the matrices maintained by the MOP are
changed. If it is necessary to respond to additional notifications, it is important to call the max_jit_mop_notify function
so that the MOP can perform any necessary maintenance with respect to input and output matrices, as demonstrated
by the jit.notify SDK example. These methods can be overridden using the MAX_JIT_MOP_FLAGS_OWN_CLEAR
and MAX_JIT_MOP_FLAGS_OWN_NOTIFY flags, respectively, when calling the max_jit_classex_mop_wrap() function.
Object registration and notification is covered in detail in a future chapter, but the jit.notify notify method is provided as
an example.
// s is the servername, msg is the message, ob is the server object pointer,
// and data is extra data the server might provide for a given message
void max_jit_notify_notify(
t_max_jit_notify *x, t_symbol *s, t_symbol *msg, void *ob, void *data)
{
if (msg==gensym("splat")) {
post("notify: server=%s message=%s",s->s_name,msg->s_name);
if (!data) {
error("splat message NULL pointer");
return;
}
// here’s where we output using the rightmost outlet
// we just happen to know that "data" points to a t_atom[3]
max_jit_obex_dumpout(x,msg,3,(t_atom *)data);
} else {
// pass on to the default Max MOP notification method
max_jit_mop_notify(x,s,msg);
}
}
Cycling ’74
100 Matrix Operator Details
By default, adapt and outputmode attributes are added to the MOP Max Wrapper. These attributes determine
whether or not to adapt to incoming matrix attributes, and whether or not the output should calculate a new out-
put matrix, output the last calculated matrix (freeze), pass on the input matrix (bypass). To prevent the addition
of the default attributes for adapt and outputmode, you can use the MAX_JIT_MOP_FLAGS_OWN_ADAPT, and
MAX_JIT_MOP_FLAGS_OWN_OUTPUTMODE flags when calling the max_jit_classex_mop_wrap() function. To de-
fine your own attributes, you would follow the same means of defining any attributes for a Max wrapper class with the
appropriate attribute name you wish to override.
For many types of operations, it's not required to fully override the default jit_matrix method and any adaptation. If your
object simply needs to override the way in which the Jitter class' matrix_calc method and outlet functions are called, you
can do so by defining an mproc method, which will be called instead of the default behavior. The jit.3m SDK project is
an example where after it calls the Jitter class' matrix_calc method, it queries the Jitter class' attributes and outputs max
messages rather than the default jit_matrix message output.
void max_jit_3m_mproc(t_max_jit_3m *x, void *mop)
{
t_jit_err err;
// call internal Jitter object’s matrix_calc method
if (err=(t_jit_err) jit_object_method(
max_jit_obex_jitob_get(x),
_jit_sym_matrix_calc,
jit_object_method(mop,_jit_sym_getinputlist),
jit_object_method(mop,_jit_sym_getoutputlist)))
{
// report error if present
jit_error_code(x,err);
} else {
// query Jitter class and makes outlet calls
max_jit_3m_bang(x);
}
}
As we discussed in the Matrix Operator Quick Start, inside your Max class' constructor you need to allocate the matrices
necessary for the MOP inputs and outputs, the corresponding matrix inlets and outlets, process matrix arguments and
other MOP setup. And in your destructor, you need to free oup MOP resources. Typically you would accomplish this all
with the standard max_jit_mop_setup_simple() and max_jit_mop_free() functions, however there are some instances
where you may need to introduce custom behavior.
The max_jit_mop_setup_simple() function calls max_jit_mop_inputs() and max_jit_mop_outputs() to define any neces-
sary proxy inlets, outlets, and internal matrices. The listing for these functions are provided below to illustrate the default
behavior, and a few SDK projects we recommend investigating further are jit.scissors, jit.glue, jit.pack, and jit.unpack.
t_jit_err max_jit_mop_inputs(void *x)
{
void *mop,*p,*m;
long i,incount;
t_jit_matrix_info info;
t_symbol *name;
Cycling ’74
23.21 The Max Class Constructor/Destructor 101
The max_jit_mop_setup_simple() function calls max_jit_mop_matrix_args() to read any matrix arguments, and if present
send them to any linked inputs/outputs and disable the adapt attribute. The listing is provided below to illustrate the
default behavior.
t_jit_err max_jit_mop_matrix_args(void *x, long argc, t_atom *argv)
{
void *mop,*p,*m;
long incount,outcount,attrstart,i,j;
t_jit_matrix_info info,info2;
if (!(mop=max_jit_obex_adornment_get(x,_jit_sym_jit_mop)))
return JIT_ERR_GENERIC;
incount = jit_attr_getlong(mop,_jit_sym_inputcount);
outcount = jit_attr_getlong(mop,_jit_sym_outputcount);
jit_matrix_info_default(&info);
attrstart = max_jit_attr_args_offset(argc,argv);
if (attrstart&&argv) {
jit_atom_arg_getlong(&info.planecount, 0, attrstart, argv);
jit_atom_arg_getsym(&info.type, 1, attrstart, argv);
i=2; j=0;
while (i<attrstart) { //dimensions
jit_atom_arg_getlong(&(info.dim[j]), i, attrstart, argv);
i++; j++;
}
Cycling ’74
102 Matrix Operator Details
if (j) info.dimcount=j;
jit_attr_setlong(mop,_jit_sym_adapt,0); //adapt off
}
jit_attr_setlong(mop,_jit_sym_outputmode,1);
for (i=2;i<=incount;i++) {
if ((p=jit_object_method(mop,_jit_sym_getinput,i)) &&
(m=jit_object_method(p,_jit_sym_getmatrix)))
{
jit_object_method(m,_jit_sym_getinfo,&info2);
if (jit_attr_getlong(p,_jit_sym_typelink)) {
info2.type = info.type;
}
if (jit_attr_getlong(p,_jit_sym_planelink)) {
info2.planecount = info.planecount;
}
if (jit_attr_getlong(p,_jit_sym_dimlink)) {
info2.dimcount = info.dimcount;
for (j=0;j<info2.dimcount;j++) {
info2.dim[j] = info.dim[j];
}
}
max_jit_mop_restrict_info(x,p,&info2);
jit_object_method(m,_jit_sym_setinfo,&info2);
}
}
for (i=1;i<=outcount;i++) {
if ((p=jit_object_method(mop,_jit_sym_getoutput,i)) &&
(m=jit_object_method(p,_jit_sym_getmatrix)))
{
jit_object_method(m,_jit_sym_getinfo,&info2);
if (jit_attr_getlong(p,_jit_sym_typelink)) {
info2.type = info.type;
}
if (jit_attr_getlong(p,_jit_sym_planelink)) {
info2.planecount = info.planecount;
}
if (jit_attr_getlong(p,_jit_sym_dimlink)) {
info2.dimcount = info.dimcount;
for (j=0;j<info2.dimcount;j++) {
info2.dim[j] = info.dim[j];
}
}
max_jit_mop_restrict_info(x,p,&info2);
jit_object_method(m,_jit_sym_setinfo,&info2);
}
}
return JIT_ERR_NONE;
}
Cycling ’74
Chapter 24
OB3D QuickStart
The purpose of this chapter is to give a quick and high level overview of how to develop a simple Jitter OpenGL object
which draws geometry within a named rendering context - we refer to such an object as an OB3D.
For this task, we will use the jit.gl.simple SDK example. More details such as how to make an OpenGL object which
deals with resources such as display lists and textures, wishes to support matrix input/output, or needs greater access
to OpenGL state will appear in the following chapter. This chapter assumes familiarity with Jitter's OpenGL object suite
used from the Max patcher, as discussed in the Jitter Tutorial, and the preceding chapters on the Jitter object model and
Max wrapper classes.
Jitter OB3Ds typically are defined to have all or most of the common OB3D attributes and methods discussed in the
Group-OB3D section of the Jitter HTML object reference. These include attributes and methods to set the rendering
destination name, object name, color, lighting, texturing, modelview transform, depth buffering, polygon mode, and sev-
eral other common tasks. These common attributes and methods are added by the call to the jit_ob3d_setup() function
in your Jitter class definition, after calling jit_class_new, but typically prior to defining other methods and attributes. For
an OB3D, Jitter needs to store additional information in your object. This information is stored in an opaque pointer in
your object struct, typically named ob3d. The byte offset to your OB3D data pointer is passed into jit_ob3d_setup(). You
can override any default attributes and methods added by jit_ob3d_setup() with the following flags:
#define JIT_OB3D_NO_ROTATION_SCALE 1 « 0
#define JIT_OB3D_NO_POLY_VARS 1 « 1
#define JIT_OB3D_NO_BLEND 1 « 2
#define JIT_OB3D_NO_TEXTURE 1 « 3
#define JIT_OB3D_NO_MATRIXOUTPUT 1 « 4
#define JIT_OB3D_AUTO_ONLY 1 « 5
#define JIT_OB3D_DOES_UI 1 « 6
#define JIT_OB3D_NO_DEPTH 1 « 7
#define JIT_OB3D_NO_ANTIALIAS 1 « 8
#define JIT_OB3D_NO_FOG 1 « 9
#define JIT_OB3D_NO_LIGHTING_MATERIAL 1 « 10
#define JIT_OB3D_HAS_LIGHTS 1 « 11
#define JIT_OB3D_HAS_CAMERA 1 « 12
#define JIT_OB3D_IS_RENDERER 1 « 13
#define JIT_OB3D_NO_COLOR 1 « 14
Aside from the attributes and methods added to your class by jit_ob3d_setup(), you need to define a private, untyped
method bound to the symbol ob3d_draw. This method is where your object does all its drawing. It is called by the
standard OB3D draw and drawraw methods. The OB3D draw method sets up all of the OpenGL state associated with the
common OB3D attributes before calling your private ob3d_draw method. The drawraw method simply sets the context
Cycling ’74
104 OB3D QuickStart
before calling your private ob3d_draw method. Because OB3Ds support being named for use within jit.gl.sketch∗'s
drawobject command, you must also add a private, untyped "register" method associated with the jit_object_register()
function. Let's examine the ∗jit.gl.simple SDK project as an example:
t_jit_err jit_gl_simple_init(void)
{
long ob3d_flags = JIT_OB3D_NO_MATRIXOUTPUT; // no matrix output
void *ob3d;
_jit_gl_simple_class = jit_class_new("jit_gl_simple",
(method)jit_gl_simple_new, (method)jit_gl_simple_free,
sizeof(t_jit_gl_simple),0L);
// set up object extension for 3d object, customized with flags
ob3d = jit_ob3d_setup(_jit_gl_simple_class,
calcoffset(t_jit_gl_simple, ob3d),
ob3d_flags);
// define the OB3D draw method. called in automatic mode by
// jit.gl.render or otherwise through ob3d when banged. this
// method is A_CANT because our draw setup needs to happen
// in the ob3d beforehand to initialize OpenGL state
jit_class_addmethod(_jit_gl_simple_class,
(method)jit_gl_simple_draw, "ob3d_draw", A_CANT, 0L);
// define the dest_closing and dest_changed methods.
// these methods are called by jit.gl.render when the
// destination context closes or changes: for example, when
// the user moves the window from one monitor to another. Any
// resources your object keeps in the OpenGL machine
// (e.g. textures, display lists, vertex shaders, etc.)
// will need to be freed when closing, and rebuilt when it has
// changed. In this object, these functions do nothing, and
// could be omitted.
jit_class_addmethod(_jit_gl_simple_class,
(method)jit_gl_simple_dest_closing, "dest_closing", A_CANT, 0L);
jit_class_addmethod(_jit_gl_simple_class,
(method)jit_gl_simple_dest_changed, "dest_changed", A_CANT, 0L);
// must register for ob3d use
jit_class_addmethod(_jit_gl_simple_class,
(method)jit_object_register, "register", A_CANT, 0L);
jit_class_register(_jit_gl_simple_class);
return JIT_ERR_NONE;
}
In your OB3D Jitter Class constructor, you need to pass in your rendering destination name as the first argument.
You should call the jit_ob3d_new() function with your destination name argument to initialize the OB3D data pointer,
associating it with your rendering destination. In your destructor, you need to free your OB3D data pointer with jit_←-
ob3d_free(). The jit.gl.simple constructor and destructors are below as an example.
t_jit_gl_simple *jit_gl_simple_new(t_symbol *dest_name)
{
t_jit_gl_simple *x;
// make jit object
if (x = (t_jit_gl_simple *)jit_object_alloc(_jit_gl_simple_class))
{
// create and attach ob3d
jit_ob3d_new(x, dest_name);
}
else
{
x = NULL;
}
return x;
}
void jit_gl_simple_free(t_jit_gl_simple *x)
{
// free ob3d data
jit_ob3d_free(x);
}
Cycling ’74
24.3 The OB3D draw Method 105
Your OB3D draw method, bound to the ob3d_draw symbol, is where all of your drawing code takes place. It is called
automatically when your associated jit.gl.render object receives a bang, if your automatic and enabled attributes are
turned on, as they are by default. It is also called if your Max wrapper object receives a bang, or the draw or drawraw
messages. With the exception of the drawraw message, all of the standard OB3D object state is setup prior to calling
your ob3d_draw method, so you needn't setup things like the modelview transform, color, lighting properties, texture
information, if your object doesn't have special needs. The following example from jit.gl.simple, just draws a simple
quadrilateral.
t_jit_err jit_gl_simple_draw(t_jit_gl_simple *x)
{
t_jit_err result = JIT_ERR_NONE;
// draw our OpenGL geometry.
glBegin(GL_QUADS);
glVertex3f(-1,-1,0);
glVertex3f(-1,1,0);
glVertex3f(1,1,0);
glVertex3f(1,-1,0);
glEnd();
return result;
}
Since this example is meant only to show a minimal object which draws geometry with standard OpenGL calls, there is
no texture information or vertex normals specified. However, all standard OpenGL calls should work within the ob3d_←-
draw method. This example also doesn't show matrix output, as accomplished by jit_ob3d_draw_chunk(), which will be
discussed in the following chapter on OB3D details.
For OB3Ds, the Max wrapper class has less extra work than for MOPs. In your Max wrapper class definition, you need
only add a call to the max_ob3d_setup() function to add your standard drawing methods, and the max_jit_ob3d_assist()
function as your assist method, unless you wish to define your own custom assist method. Everything else is similar to
the standard technique of wrapping a Jitter Class demonstrated in the Max Wrapper Class chapter.
void ext_main(void *r)
{
void *classex, *jitclass;
// initialize Jitter class
jit_gl_simple_init();
// create Max class
setup((t_messlist **)&max_jit_gl_simple_class,
(method)max_jit_gl_simple_new, (method)max_jit_gl_simple_free,
(short)sizeof(t_max_jit_gl_simple), 0L, A_GIMME, 0);
// specify a byte offset to keep additional information about our object
classex = max_jit_classex_setup(calcoffset(t_max_jit_gl_simple, obex));
// look up Jitter class in the class registry
jitclass = jit_class_findbyname(gensym("jit_gl_simple"));
// wrap Jitter class with the standard methods for Jitter objects
max_jit_classex_standard_wrap(classex, jitclass, 0);
// use standard ob3d assist method
addmess((method)max_jit_ob3d_assist, "assist", A_CANT,0);
// add methods for 3d drawing
max_ob3d_setup();
}
Your Max class' constructor should be similar to the standard Max wrapper constructor, but the differences worth noting
are that you should pass your first normal argument, which is the rendering destination, on to your Jitter OB3D con-
structor, and create a second outlet for matrix output, attached to your object's OB3D data. For your destructor, there
Cycling ’74
106 OB3D QuickStart
is nothing additional you need to do for OB3D. The jit.gl.simple Max class' constructor and destructor are provided as
examples.
void *max_jit_gl_simple_new(t_symbol *s, long argc, t_atom *argv)
{
t_max_jit_gl_simple *x;
void *jit_ob;
long attrstart;
t_symbol *dest_name_sym = _jit_sym_nothing;
if (x = (t_max_jit_gl_simple *) max_jit_obex_new(
max_jit_gl_simple_class, gensym("jit_gl_simple")))
{
// get first normal arg, the destination name
attrstart = max_jit_attr_args_offset(argc,argv);
if (attrstart&&argv)
{
jit_atom_arg_getsym(&dest_name_sym, 0, attrstart, argv);
}
// instantiate Jitter object with dest_name arg
if (jit_ob = jit_object_new(
gensym("jit_gl_simple"), dest_name_sym))
{
// set internal jitter object instance
max_jit_obex_jitob_set(x, jit_ob);
// add a general purpose outlet (rightmost)
max_jit_obex_dumpout_set(x, outlet_new(x,NULL));
// process attribute arguments
max_jit_attr_args(x, argc, argv);
// attach the jit object’s ob3d to a new outlet
// this outlet is used in matrixoutput mode
max_jit_ob3d_attach(x, jit_ob, outlet_new(x, "jit_matrix"));
}
else
{
error("jit.gl.simple: could not allocate object");
freeobject((t_object *)x);
x = NULL;
}
}
return (x);
}
void max_jit_gl_simple_free(t_max_jit_gl_simple *x)
{
// lookup our internal Jitter object instance and free
jit_object_free(max_jit_obex_jitob_get(x));
// free resources associated with our obex entry
max_jit_obex_free(x);
}
Cycling ’74
Chapter 25
OB3D Details
The purpose of this chapter is to fill in additional details of Jitter OpenGL, which we refer to as OB3Ds.
We will show how to disable and/or override default OB3D attributes and methods, how to support matrix input and
output, and manage resources such as textures, display lists, and shaders. This chapter assumes familiarity with the
OpenGL API and the OB3D Quick Start chapter. It is out of the scope of our documentation to cover the OpenGL API,
so for information on the OpenGL API we recommend consulting the OpenGL Red Book and the many online tutorials.
As covered in the OB3D Quick Start, Jitter OB3Ds have a large number of default attributes and methods, and require
some specific methods to be defined. This section seeks to clarify these common attributes and methods and how to
achieve custom behavior where necessary.
All Jitter OB3Ds must define a method bound to the symbol ob3d_draw. This method takes no arguments in addition to
the object struct, and should be defined with the private A_CANT type signature. The private ob3d_draw method will be
called by the standard draw, and drawraw methods that are added to every OB3D. The draw method will set up OpenGL
state associated with the default OB3D attributes before calling ob3d_draw, while the drawraw method will not.
It is possible for attributes of a Jitter OB3D or your render destination to change, requiring resources to be freed or rebuilt.
There are three methods used to communicate to an OB3D which such events happen so that the OB3D can manage
resources accordingly. They are: dest_closing, which informs an OB3D that the destination is being freed, and any
context dependent resources such as textures, display lists, and shaders should be freed; dest_changed, which informs
an OB3D that the destination has been rebuilt, and new resources can be allocated; and rebuild_geometry, which
informs an OB3D of a change in texture units or some other attribute which affects jit_gl_drawinfo_setup() and other
t_jit_gl_drawinfo related functions, such as jit_gl_texcoord, requiring geometry that uses such functions to be rebuilt.
These methods take no arguments in addition to the object struct. The dest_closing and dest_changed methods should
be defined with the private A_CANT type signature, and the rebuild_geometry method is typically defined as typed, but
without arguments, so that users have the ability to explicitly call, if deemed necessary. The jit.gl.gridshape SDK project
is a good example of these methods as it needs to free and allocate a display list as the render destination changes, and
also makes use of jit_gl_texcoord to support multi-texturing, requiring geometry to be rebuilt as the number of texture
units or other attributes change.
Cycling ’74
108 OB3D Details
Since all Jitter OB3D objects are named to support reference by name in jit.gl.sketch, and other objects, it is necessary
to add the default registration method, jit_object_register(). Object registration and notification are covered in detail in a
future chapter.
By default, each Jitter OB3D has rotate, rotatexyz, scale, and viewalign attributes added to the class by jit_ob3d_setup(),
and these attributes are used in the ob3d_draw_preamble() function to set up OpenGL state prior to calling your object's
draw method. These attributes can be disabled by using the JIT_OB3D_NO_ROTATION_SCALE flag. You can override
these attributes by defining your own attributes of the same name, however, you will need to manage any necessary
OpenGL state inside of your own draw method with the appropriate calls to glMatrixMode, glTranslate, glRotate, and
glScale.
By default, each Jitter OB3D has color, aux_color, and smooth_shading attributes added to the class by jit_ob3d_setup(),
and these attributes are used in the ob3d_draw_preamble function prior to calling your object's draw method. These
attributes can be disabled by using the JIT_OB3D_NO_COLOR flag. You can override these attributes by defining your
own attributes of the same name, however, you will need to manage any necessary OpenGL state inside of your own
draw method with the appropriate calls to glColor and glShadeModel.
By default, each Jitter OB3D has texture, capture, tex_map, tex_plane_s, and tex_plane_t attributes added to the class
by jit_ob3d_setup(), and these attributes are used in the ob3d_draw_preamble() function prior to calling your object's
draw method. These attributes can be disabled by using the JIT_OB3D_NO_TEXTURE flag. You can override these
attributes by defining your own attributes of the same name, however, you will need to manage any necessary OpenGL
state inside of your own draw method with the appropriate calls to glEnable, glTexGen, jit_gl_bindtexture, jit_gl_←-
unbindtexture, jit_gl_begincapture, and jit_gl_endcapture.
By default, each Jitter OB3D has lighting_enable, auto_material, shininess, mat_ambient, mat_diffuse, mat_specular,
and mat_emission attributes added to the class by jit_ob3d_setup(), and these attributes are used in the ob3d_←-
draw_preamble function prior to calling your object's draw method. These attributes can be disabled by using the
JIT_OB3D_NO_LIGHTING_MATERIAL flag. You can override these attributes by defining your own attributes of the
same name, however, you will need to manage any necessary OpenGL state inside of your own draw method with the
appropriate calls to glEnable, glLight, glLightModel, and glMaterial.
Cycling ’74
25.9 Overriding Fog Related Attributes 109
By default, each Jitter OB3D has fog and fog_params attributes added to the class by jit_ob3d_setup(), and these
attributes are used in the ob3d_draw_preamble function prior to calling your object's draw method. These attributes can
be disabled by using the JIT_OB3D_NO_FOG flag. You can override these attributes by defining your own attributes of
the same name, however, you will need to manage any necessary OpenGL state inside of your own draw method with
the appropriate calls to glEnable, glHint, and glFog.
By default, each Jitter OB3D has poly_mode, cull_face, point_size, and line_width attributes added to the class by jit←-
_ob3d_setup(), and these attributes are used in the ob3d_draw_preamble function prior to calling your object's draw
method. These attributes can be disabled by using the JIT_OB3D_NO_POLY_VARS flag. You can override these
attributes by defining your own attributes of the same name, however, you will need to manage any necessary OpenGL
state inside of your own draw method with the appropriate calls to glPolygonMode, glEnable, glCullFace, glPointSize,
and glLineWidth.
By default, each Jitter OB3D has blend_mode and blend_enable attributes added to the class by jit_ob3d_setup(),
and these attributes are used in the ob3d_draw_preamble function prior to calling your object's draw method. These
attributes can be disabled by using the JIT_OB3D_NO_BLEND flag. You can override these attributes by defining your
own attributes of the same name, however, you will need to manage any necessary OpenGL state inside of your own
draw method with the appropriate calls to glEnable and glBlendFunc.
By default, each Jitter OB3D has depth_enable and antialias attributes added to the class by jit_ob3d_setup(), and these
attributes are used in your ob3d_draw_preamble function prior to calling your object's draw method. These attributes can
be disabled by using the JIT_OB3D_NO_DEPTH and JIT_OB3D_NO_ANTIALIAS flags, respectively. You can override
these attributes by defining your own attributes of the same name, however, you will need to manage any necessary
OpenGL state inside of your own draw method with the appropriate calls to glEnable and glHint.
By default, each Jitter OB3D has matrixoutput and automatic attributes added to the class by jit_ob3d_setup(), and these
attributes are used in the ob3d_draw_preamble function prior to calling your object's draw method. These attributes can
be disabled by using the JIT_OB3D_NO_MATRIXOUTPUT and JIT_OB3D_AUTO_ONLY flags, respectively. You can
override these attributes by defining your own attributes of the same name.
Cycling ’74
110 OB3D Details
It is possible to declare a user interface OB3D, such as jit.gl.handle. To do so, you must use the JIT_OB3D_DOES_UI
flag to jit_ob3d_setup(), and define a method bound to the symbol ob3d_ui, with the private A_CANT type signature and
prototype similar to the following example from jit.gl.handle:
t_jit_err jit_gl_handle_ui(t_jit_gl_handle *x,
t_line_3d *p_line, t_wind_mouse_info *p_mouse);
Inside your Jitter class constructor, you must call jit_ob3d_new() with a pointer to your newly allocated object, and your
render destination name. The jit_ob3d_new() function allocates an opaque structure that stores the standard OB3D
attributes and some additional OB3D state, initializing them to default values, and then setting the pointer at the byte
offset specified when calling the jit_ob3d_setup() function in your class definition. If your object supports matrix output
or simply uses the t_jit_glchunk structure when drawing, you should typically allocate your initial t_jit_glchunk in your
constructor using the jit_glchunk_new() or jit_glchunk_grid_new() functions. Use of the t_jit_glchunk structure and matrix
output is described later in this chapter. Similarly, your OB3D Jitter class destructor must call jit_ob3d_free() to free the
opaque structure used for common OB3D state, free any allocated instances of t_jit_glchunk with jit_glchunk_free(),
and free any other resources allocated such as display lists or textures.
The ob3d_draw method is where all the drawing in your object should take place. It is also where you should typically
allocate context dependent resources or query the context state, since you know that your context is valid and has been
set. For the most part, the drawing you will perform in your ob3d_draw method will be pure and simple OpenGL, though
there are a few caveats which we will cover.
Since Jitter is a general purpose matrix processing framework, it makes sense that you would have the ability to pass
geometry information through a Jitter network as matrices if your geometry is well suited to a matrix representation. The
cells of your matrix can hold vertex information such as position, texture coordinates, normal vectors, color, and edge
flags, and are documented in the "Geometry Under The Hood" Jitter Tutorial. You also have the option of specifying
a connections matrix to reference the connectivity of the vertices if it is not implicit in the matrix representation, and a
drawing primitive to use when drawing the vertices.
All this information, and whether or not the geometry matrix should be rendered immediately or sent through the Jitter
network is managed with the t_jit_glchunk. An SDK example which demonstrates the use of t_jit_glchunk is jit.gl.←-
gridshape. The t_jit_glchunk structure along with the vertex matrix it contains is allocated by the jit_glchunk_new() or
jit_glchunk_grid_new() functions, freed with the jit_glchunk_delete() function, and drawn with the jit_ob3d_draw_chunk()
function. For reference, the t_jit_glchunk structure and relevant chunk flags are provided below:
// jit_glchunk is a public structure to store one
// gl-command’s-worth of data, in a format which
// can be passed easily to glDrawRangeElements.
typedef struct _jit_glchunk
{
t_symbol *prim; // GL_TRI_STRIP, GL_TRIANGLES, etc.
t_jit_object *m_vertex; // jit_matrix of xyzst... data.
t_symbol *m_vertex_name; // vertex matrix name
Cycling ’74
25.18 OB3D OpenGL Caveats 111
While you can use any standard Open GL calls inside of your ob3d_draw method. There are a few things worth
noting to follow Jitter conventions. The first of which is the binding of texture coordinates. Since Jitter OB3Ds sup-
port multi-texturing by default, it is not necessarily satisfactory to submit only one texture coordinate with glTexCoord.
Jitter provides some utility routines to set the texture coordinates for as many texture units which are bound, jit_gl←-
_texcoord(1/2/3)(f/fv). Determining how many texture units have been bound by the default OB3D attributes requires
some overhead, so rather than perform this overhead with every jit_gl_texcoord call, the jit_gl_texcoord functions take
a t_jit_gl_drawinfo struct as an argument. This struct can be setup once before rendering many vertices with the jit←-
_gl_drawinfo_setup function. Example use of jit_gl_texcoord and jit_gl_drawinfo_setup is in the jit.gl.videoplane SDK
project. Another Jitter specific mechanism is the means to bind textures using named instances of jit.gl.texture. It is
possible to create and bind your own textures in an OB3D, but you must then perform all maintenance instead of relying
on jit.gl.texture to handle this work for you. To bind and unbind an instance of jit.gl.texture, you should call the jit_←-
gl_bindtexture and jit_gl_unbindtexture functions, which take a t_jit_gl_drawinfo argument, a symbol with the name of
the jit.gl.texture instance, and an integer for which texture unit to bind. Unlike binding ordinary textures in OpenGL, it is
important to unbind instances of jit.gl.texture, or else problems may arise.
Though the default OB3D attributes are typically relevant to the code which is automatically handled for your object
prior to calling the ob3d_draw method, it is sometimes necessary to access these values. Since the default OB3D
attributes are stored in an opaque ob3d struct member, they are not accessible by your object with a simple struct
pointer dereference. Instead, you need to use the jit_attr_get∗ functions to access these attributes. You should pass in
your object struct as the first argument to these functions rather than your ob3d struct member. For example:
float pos[3];
jit_attr_getfloat_array(x,gensym("position"),3,pos);
Note that if you are acquiring this value often, it is preferable to generate the symbol in advance rather than generate
the symbol for every call.
From within the ob3d_draw, dest_closing, and dest_changed methods, the rendering context has always been set, and
you can get a handle to the native context using either the aglGetCurrentContext or wglGetCurrentContext functions.
One can also in these methods use standard OpenGL glGet∗ functions to determine the context's OpenGL state, such
as the viewport, transformation matrix. It is not recommended to try and acquire the native context from other methods,
or query the OpenGL state as it may not be valid.
Cycling ’74
112 OB3D Details
It is important to recognize that OpenGL state is persistent, and that there may be objects which rely on OpenGL state
that are drawn after your object draws itself. If your object makes any changes to OpenGL state that might affect objects
that follow, you should restore the OpenGL state to whatever it was before your routine was called. For example, if
your object changes the texture transformation matrix, you should push and pop the texture transformation matrix with
glMatrixMode, glPushMatrix, and glPopMatrix, to prevent any problems with other objects.
As mentioned in the OB3D Quick Start, in your Max wrapper class definition, you need only add a call to the max←-
_ob3d_setup() function to add your standard drawing methods, and the max_jit_ob3d_assist() function as your assist
method, unless you wish to define your own custom assist method. Everything else is similar to the standard technique
of wrapping a Jitter Class demonstrated in the Max Wrapper Class chapter. Please consult the OB3D Quick Start
chapter and the jit.gl.simple SDK project for all necessary information related to the OB3D Max wrapper class.
Sometimes it is desirable for an OB3D also support incoming matrices as is the case with jit.gl.videoplane or jit.gl.mesh.
It is not recommended to mix and match OB3Ds with MOPs. Conflicts arise with respect to arguments, standard inlets
and outlets. Instead, if you wish to support matrix input in your OB3D, you should simply add to your Jitter class a
method bound to the symbol jit_matrix, and handle the incoming matrix data according to your needs - for example
as texture data in the case of jit.gl.videoplane, or geometry data in the case of jit.gl.mesh. The jit.gl.videoplane SDK
project provides an example of an OB3D which also supports matrix input. When it is necessary to have multiple input
matrices, this is typically managed by either declaring alternately named methods for each input, or exposing an attribute
that specifies which input the jit_matrix method assumes it is being called with. Note that this requires additional logic
within the Max wrapper class to map to inlets, as it is not handled automatically.
Cycling ’74
Chapter 26
The details of these threads are highlighted in the Max documentation and the article, "Event Priority in Max (Scheduler
vs. Queue)". In this chapter, we won't cover all these details and restrict our discussion to the scheduler (which when
overdrive is on runs in a separate and high priority thread) and the low priority queue (which always runs in the main
application thread). As far as Jitter is concerned, we won't consider the real time audio thread or the case of scheduler
in audio interrupt, where the scheduler runs in this real time audio thread.
By default, Jitter performs all drawing and matrix processing in the main application thread, with events serviced from
the low priority queue. The reason for this low priority processing is to prevent high timing events such as note triggering
or audio DSP from suffering timing problems due to visual processing. Jitter also exploits the low priority queue as a
mechanism for graceful temporal downsampling of the visual stream in the instance that the processing requested is too
demanding to be calculated in real-time. This results in dropped frames in the output when the demands cant be met.
With audio, it's not sufficient to just drop frames of samples, since there will be an audible click, but with images, the last
image will persist if a new one isn't generated at some fixed sampling rate.
The mechanisms which enforce execution of Jitter drawing and matrix processing from within the low priority queue
we will call "defer" and "usurp". The defer mechanism will take any high priority events and create a corresponding low
priority event at the end of the low priority queue. The defer mechanism ensures that the events will not be executed from
the high priority scheduler thread, but does not prevent scheduler backlog with the temporal downsampling mentioned
above. To accomplish this, the usurp mechanism mush be used. The usurp mechanism will use no more than one low
priority queue element for the task requested (either a method call or attribute setter). The way usurp works is that if
there is no pending event for the method or attribute call, a new event is placed at the end of the low priority queue. If
there is already an event pending, the usurp mechanism will not place a new event on the end of the low priority queue,
but rather "usurp" the arguments for the event waiting to being passed to the method or attribute call. This way, if a
high priority metronome is rapidly sending values to set an attribute, while the initial low priority event is waiting to be
processed, the value to be set is constantly being updated ("usurped") and only the value at the time of servicing the
event will be used.
It is important to note that the defer and usurp mechanisms only work as called from within the Max patcher. For any
methods which are called from a text based programming language, such as C, Java, or JavaScript, the defer and usurp
mechanisms are bypassed. This may be something you need to pay attention to and handle yourself if you are making
such calls from a text based programming language and need the defer or usurp behavior.
Cycling ’74
114 Scheduler and Low Priority Queue Issues
When defining a method in Jitter, there is the possibility to define a type signature for the method just as one would do
in Max. Typical type signatures include typical atom elements such as A_LONG, A_FLOAT, and A_SYM; or the corre-
sponding default value versions A_DEFLONG, A_DEFFLOAT, A_DEFSYM; or the variable argument version A_GIMME
which provides a list of atoms and the number of atoms provided; or the private and untyped status of A_CANT used for
methods which are not exposed to the patcher and require additional C function prototype information in order to call.
While these type signatures can be used within Jitter objects, most methods exposed to the patcher interface make use
of either the defer or usurp mechanism as defined by two new type signatures A_DEFER_LOW or A_USURP_LOW.
Methods defined with the A_DEFER_LOW, or A_USURP_LOW type signatures should conform to the same variable ar-
gument prototype as A_GIMME methods, but behind the scenes, Jitter will make use of the defer and usurp mechanism
to enforce the appropriate behavior.
An example of two methods from jit.gl.videoplane which use these mechanisms is below:
// add a usurping jit_matrix method
jit_class_addmethod(_jit_gl_videoplane_class, (method)jit_gl_videoplane_jit_matrix, "jit_matrix", A_USURP_LOW,
0);
// add a deferred sendtexture method
jit_class_addmethod(_jit_gl_videoplane_class, (method)jit_gl_videoplane_sendtexture, "sendtexture",
A_DEFER_LOW, 0);
The implementation of these methods is below:
void jit_gl_videoplane_jit_matrix(t_jit_gl_videoplane *x, t_symbol *s, int argc, t_atom *argv)
{
t_symbol *name;
void *m;
t_jit_matrix_info info;
long dim[2];
if ((name=jit_atom_getsym(argv)) != _jit_sym_nothing) {
m = jit_object_findregistered(name);
if (!m) {
error("jit.gl.videoplane: couldn’t get matrix object!");
return;
}
}
if (x->texture) {
jit_object_method(m, _jit_sym_getinfo, &info);
jit_attr_getlong_array(x->texture,_jit_sym_dim,2,dim);
jit_object_method(x->texture,s,s,argc,argv);
jit_attr_setsym(x,ps_texture,x->texturename);
}
}
void jit_gl_videoplane_sendtexture(t_jit_gl_videoplane *x, t_symbol *s, int argc, t_atom *argv)
{
if (x->texture) {
s = jit_atom_getsym(argv);
argc--;
if (argc)
argv++;
else
argv = NULL;
object_method_typed(x->texture,s,argc,argv,NULL);
}
}
From inspecting the header files, you may note that there are also A_DEFER and A_USURP type signatures, but these
should be considered obsolete, as they make use of the problematic deferral strategy of placing the event at the front of
the low priority queue and have the potential of reversing message sequencing.
Unlike methods, attributes do not make use of type signatures for their getter and setter accessor methods. Instead they
should always be prototyped similar to A_GIMME, but with an attribute object being passed in place of the traditional
method symbol pointer of the A_GIMME signature. So the way you can specify to use the defer and usurp mechanisms
for attribute accessors are through the attribute flags argument to the attribute constructor. For the getter accessor
Cycling ’74
26.4 Using Defer and Usurp in the Max Wrapper Object 115
method, you can use JIT_ATTR_GET_DEFER_LOW or JIT_ATTR_GET_USURP_LOW flags. For the setter accessor
method, you can use JIT_ATTR_SET_DEFER_LOW or JIT_ATTR_SET_USURP_LOW flags.
You may have noticed that like previous code example, all Jitter object attributes which are not private have been
defined with getter accessors which use the defer mechanism (JIT_ATTR_GET_DEFER_LOW) and setter accessors
which use the usurp mechanism (JIT_ATTR_SET_USURP_LOW). This is the recommended style of exposing Jitter
object attributes to the patcher, since there are many cases where at high priority an attribute is set repeatedly and
we want both the latest high priority value when the next calculation is made at low priority and no low priority queue
backlog from generating more events at high priority than can be processed at low priority. The defer mechanism is
used for getter accessor methods so that every attribute query results in a corresponding output message out the dump
outlet. Otherwise certain patcher logic could easily become confused. If a different behavior is required by the Max
programmer, they can make use of the jit.qball object to force either the defer or usurp mechanisms to be used for their
message stream.
Most of the above is also true when declaring methods and attributes in the Max wrapper object, however
the function calls which are used are slightly different. You must use the special max object function calls
max_addmethod_defer_low() and max_addmethod_usurp_low() for methods, and max_jit_classex_addattr() for
attributes. Below are examples from jit.matrixset. Note that there is no type signature provided for either
max_addmethod_defer_low() or max_addmethod_usurp_low().
// add a deferred "exportmovie" method
max_addmethod_defer_low((method)max_jit_matrixset_export_movie, "exportmovie");
// add a usurped outputmatrix method
max_addmethod_usurp_low((method)max_jit_matrixset_outputmatrix, "outputmatrix");
// add index attribute
attrflags = JIT_ATTR_GET_DEFER_LOW | JIT_ATTR_SET_USURP_LOW ;
attr = jit_object_new(_jit_sym_jit_attr_offset,"index",_jit_sym_long,attrflags,
(method)0L,(method)0L,calcoffset(t_max_jit_matrixset,index));
max_jit_classex_addattr(p,attr);
The bang method for Jitter MOP objects uses the usurp mechanism to drop frames when the number of bang messages
cannot be handled in real time. However, jit.gl.render's bang method does not behave this way, and instead uses the
defer mechanism. At first this might seem counterintuitive, however, because rendering in OpenGL with jit.gl.render
uses a group of messages to perform erasing, any non automatic drawing of objects, and then a drawing of automatic
clients and a swap to the screen with the bang method, it is not an atomic action (i.e. requires a sequence of different
events rather than a single event). Since the usurp mechanism is method or attribute specific with regard to the events
which are being usurped, it only works for atomic actions. For this reason, it is important for users to perform some drop
framing behavior before triggering the message sequence, typically accomplished with qmetro or jit.qball. If your object
has some operation which requires a sequence of events in a similar fashion as jit.gl.render, then it would be best to use
the defer mechanism rather than the usurp mechanism for relevant methods.
Cycling ’74
116 Scheduler and Low Priority Queue Issues
There are instances where the user does not wish to be limited to processing Jitter matrices at low priority, such as
when Jitter matrices are used for tasks other than realtime image processing–for example, parameter interpolation or
matrices containing audio data. For these tasks, the jit.qfaker object is provided for advanced users which are aware of
the potential problems involved in bypassing these low priority mechanisms. As mentioned above, when programming
in a text based language, these mechanisms aren't used and all method and attribute accessor calls are synchronous.
Therefore there typically isn't a need to consider overriding this behavior from a text based language. However, for
certain externals which wish to simulate the jit.qfaker behavior, we expose the max_jit_queuestate() function to override
Jitter's detection of queue state for the defer and usurp mechanisms. It is also possible to query what jitter believes
the queue state to be with the max_jit_getqueuestate() function. This is the function employed by the defer and usurp
mechanisms. The source code for these functions is below for reference.
long max_jit_queuestate(long state)
{
long rv=_max_jit_queuestate;
_max_jit_queuestate = (state!=0);
return rv;
}
long max_jit_getqueuestate(void)
{
// always return true if faking
if (_max_jit_queuestate) return 1;
return !sched_isinpoll();
}
Cycling ’74
Chapter 27
In Jitter, matrices are passed around as named references between Max objects.
This named reference is created since Jitter registers these matrices with the corresponding name using the
jit_object_register() function. Object registration is useful for a few reasons. First, registered matrices can be resolved
by name using the jit_object_findregistered() function. Secondly, registered objects can sent event notification to clients
who have attached to them using jit_object_attach(). Lastly, under certain circumstances, the object registration process
can be used to have multiple external references to a single instance of an object as is the case with jit.matrix.
To register an object, one can use the jit_object_register() function, which is equivalent to the Max object_register()
function in the namespace associated with gensym("jitter"). Traditionally in Jitter, we bind jit_object_register() to the
"register" method for an object and use jit_object_method() to call this method. For example, from the jit.notify SDK
example:
// allocate the Jitter object
if (o=jit_object_new(gensym("jit_notify"))) {
...
// generate a unique name
x->servername = jit_symbol_unique();
// register the object with the given name
jit_object_method(o,_jit_sym_register,x->servername);
...
}
If not using a specific name, it is good to use the jit_symbol_unique() function as above to generate a unique name
which is slated for re-use once a registered object is freed. This prevents excess memory usage by the symbol table as
associated with these unique names.
If you wish the object to have multiple references to a single instance with some name, as is common with the jit.matrix
object, it is essential to use the return value of jit_object_register() in any instance where the object pointer is saved
after registration. This is because if the registered object with the same class already exists, the object attempting to be
registered will be freed, and the already registered object of the same class will be returned, its reference count having
been incremented. This is not typically an issue outside of registering jit.matrix objects, although you may have a need
for this type of implementation in other situations. Most other situations in which object registration is used within Jitter
only expects and/or permits a single instance to be registered. In the above example, we know that this is safe to do, as
we are using jit_symbol_unique() to generate a unique name.
It is also possible to unregister named objects, with the jit_object_unregister() function, but typically this is handled for
you when your object is freed, or if your object is registered again with a different name. This is not often used in the
Jitter code base except within these contexts.
Cycling ’74
118 Jitter Object Registration and Notification
Registered objects can be found by name using the jit_object_findregistered() function. For example named matrices
are resolved using this function. Most Matrix Operator objects have this done for them by the default MOP code,
but for example any MOP which has its own jit_matrix method, such as the jit.pack SDK example will make use of
jit_object_findregistered() inside its jit_matrix method:
// get our matrix name from the atom arguments provided
matrixname = jit_atom_getsym(argv);
// look up based on name
matrix = jit_object_findregistered(matrixname);
// make sure that it is a valid pointer and has a "class_jit_matrix" method which returns 1
if (matrix&&jit_object_method(matrix, _jit_sym_class_jit_matrix)) {
...
}
Once an object has been registered, it can be considered a server to which clients attach to be notified of various
events. To attach to a named object, use the the jit_object_attach() function. Similarly to detach from a named object,
use the jit_object_detach() function. It is typical to detach from a server in your object's destructor, or any time your
object is switching which server it is attached to. For your client object to receive any notification from the server object,
it is important for your object to have defined a "notify" method which will receive the notification from all objects it is
attached to.
Below is the jit.notify SDK example's max wrapper object's notify method, which receives some atom values from its
internal Jitter object instance. Since this object is a Matrix Operator, it is important in the following example that jit.←-
notify calls the max_jit_classex_mop_wrap() function with the MAX_JIT_MOP_FLAGS_OWN_NOTIFY flag to override
the default MOP notify method, and that we pass on all other messages to the standard max_jit_mop_notify() method
so that the default MOP code is informed of any changes to the input and output matrices.
// s is the servername, msg is the message, ob is the server object pointer,
// and data is extra data the server might provide for a given message
void max_jit_notify_notify(t_max_jit_notify *x, t_symbol *s, t_symbol *msg, void *ob, void *data)
{
if (msg==gensym("splat")) {
post("notify: server=%s message=%s",s->s_name,msg->s_name);
if (!data) {
error("splat message NULL pointer");
return;
}
// here’s where we output using the rightmost outlet
// we just happen to know that "data" points to a t_atom[3]
// alternately you could use max_jit_obex_dumpout_get just to get
// the outlet pointer
max_jit_obex_dumpout(x,msg,3,(t_atom *)data);
} else {
// since we are a MOP, we are also attached to all the matrices for each input/output
// so we need to deal with this by calling the default mop notify method
// (this is how mops handle their matrices getting new names/freed/modified)
max_jit_mop_notify(x,s,msg);
}
}
If you are making an object which is to be registered, and wish to send custom notification to clients in addition to the
default notification that attributes send to all clients when the attribute is modified, and the default object free notification,
then you will want to use the jit_object_notify() function. This function lets you determine a message name to use for
notification and optionally specify additional, but untyped data to all clients. If you choose to send additional data to
Cycling ’74
27.4 Notifying Clients 119
clients, it is necessary for all client code to know how to unpack this information. Below is the example from the jit.notify
SDK example which uses the notification mechanism to send some data to its max wrapper object:
t_atom foo[3];
jit_atom_setlong(&foo[0],1);
jit_atom_setlong(&foo[1],2);
jit_atom_setlong(&foo[2],3);
jit_object_notify(x,gensym("splat"), foo);
Cycling ’74
120 Jitter Object Registration and Notification
Cycling ’74
Chapter 28
When developing for Jitter in C, the functionality of pre-existing Jitter objects can be used.
In this chapter, we'll briefly examine instantation and incorporation of the features of the jit.movie and jit.qt.record objects
from your C code.
Using an object like t_jit_qt_movie from your own code is fairly straightforward. Since it's a standard Jitter object, we
can use jit_object_new() and jit_object_free() for instantiation and freeing, jit_object_method() for sending messages,
and jit_attr_get... and jit_attr_set... for getting and setting attributes.
For instance, in the following code snippet, we'll create a t_jit_qt_movie object, read a pre-specified movie from disk,
and decompress its first frame into a matrix, set to the native size of the movie.
void jit_foo_read_first_movie_frame(
t_jit_foo *x, t_symbol *s, long ac, t_atom *av)
{
void *qtmovie;
// create the t_jit_qt_movie object, sized to 1x1
qtmovie = jit_object_new(gensym("jit_qt_movie"), 1, 1);
if (qtmovie) {
t_atom rv; // will contain rvarr, with any return values
// from our "read" call
t_object *rvarr; // the t_atomarray with the actual
// return values
// turn off autostart
jit_attr_setlong(qtmovie, gensym("autostart"), 0);
// read the movie, just pass in the args to our function
object_method_typed(qtmovie, gensym("read"), ac, av, &rv);
// check the return value & verify that the movie loaded
if (rvarr = jit_atom_getobj(&rv)) {
long rvac = 0;
t_atom *rvav = NULL;
object_getvalueof(rvarr, &rvac, &rvav);
if (rvac && rvav) {
// just as in Max, we get a list: "filename success";
// success of 1 means the read was successful
if (rvac > 1 && jit_atom_getlong(rvav + 1)) {
long dim[2];
void *matrix;
t_jit_matrix_info info;
// get our movie’s native dims
jit_attr_getlong_array(qtmovie, gensym("movie_dim"),
2, dim);
// set the t_jit_qt_movie’s dim to match
jit_object_method(qtmovie,_jit_sym_dim,dim[0],dim[1]);
Cycling ’74
122 Using Jitter Objects in C
Naturally, we could also set the t_jit_qt_movie object's time attribute, or call its or frame method, to recall an arbitrary
point in time. In fact, nearly every documented method and attribute of the jit.movie object, as it functions in the Max
interface, is available from C. The exceptions are those functions implemented in the Max wrapper object, such as
framedump.
Cycling ’74
Chapter 29
The Jitter File Format (JXF) stores matrix data in a binary (not human-readable) form.
When using Jitter you can create JXF files by sending the write message to a jit.matrix object. Conversely you can read
JXF files from disk using the read message. This section will cover first the API functions that one can use from C to
read and write JXF files. Then it will break down the file format at the bit level.
Most Jitter users do not need or want to know about the internal binary format of a JXF-file. Even users who want to
read and write JXF-files from C do not need to know the internal details if they use the functions of the Jitter API for the
binary interface. Not only is the API more convenient, but using the functions provided by Cycling '74 may protect your
code from having to be altered in the future in the event of a specification change.
There are two primary functions one should use to read data from a JXF file. jit_bin_read_header() reads the version
number and the size of the file from the header, and has the following signature:
t_jit_err jit_bin_read_header(t_filehandle fh, ulong *version, long *filesize)
jit_bin_read_matrix() imports matrix data from a file to a matrix, resizing the matrix if necessary, and has the following
signature:
t_jit_err jit_bin_read_matrix(t_filehandle fh, void *matrix)
Here’s a chunk of code that shows how to read a matrix from disk:
if (!(err=path_opensysfile(filename, path, &fh, READ_PERM))) {
//all is well
} else {
error("jit.matrix: can’t open file %s",name->s_name);
goto out;
}
if (jit_bin_read_header(fh,&version,&filesize)) {
error("jit.matrix: improper file format %s",name->s_name);
sysfile_close(fh);
goto out;
}
if (jit_bin_read_matrix(fh,matrix)) {
error("jit.matrix: improper file format %s",name->s_name);
sysfile_close(fh);
goto out;
}
sysfile_close(fh);
Cycling ’74
124 JXF File Specification
Similarly there are two functions one should use when writing data to a JXF file. jit_bin_write_header() writes a header
to a file, and has the following signature:
t_jit_err jit_bin_write_header(t_filehandle fh, long filesize)
Here’s a section of code that shows how you might write a file with one matrix. Note that the initial filesize argument to
jit_bin_write_header() is bogus, but that the header is written again at the end of the operation when the filesize can be
determined from the file position after writing the matrix.
if (err=path_createsysfile(filename, path, type, &fh)) {
error("jit.matrix: could not create file %s",name->s_name);
goto out;
}
if (jit_bin_write_header(fh,0)) {
error("jit.matrix: could not write header %s", matrixName->s_name);
sysfile_close(fh);
goto out;
}
if (jit_bin_write_matrix(fh,pointerToMatrix)) {
error("jit.matrix: could not write matrix %s", matrixName->s_name);
sysfile_close(fh);
goto out;
}
sysfile_getpos(fh, &position);
sysfile_seteof(fh, position);
if (jit_bin_write_header(fh,position)) {
error("jit.matrix: could not write header %s",
matrixName->s_name);
sysfile_close(fh);
goto out;
}
sysfile_close(fh);
The internal format of JXF-files is based on the Interchange File Format (IFF) ( http://en.wikipedia.←-
org/wiki/Interchange_File_Format). An IFF file is built up from chunks. All data in IFF files is big-endian.
Several convenience macros defined in jit.byteorder.h are available to help convert numbers to the proper format before
and after they're written to and read from a JXF file: BE_I32() can be called on 32-bit integers, BE_F32() on 32-bit floats,
and BE_F64() on 64-bit doubles.
Each chunk in an IFF file begins with a four character Type ID. This is followed by a 32-bit unsigned integer specifying
the size of the chunk content in bytes. In a JXF file, the 32-bit integer part of the first chunk tells us the size of the file,
and all the subsequent chunks, which begin immediately after the first chunk, contain matrices. In the future chunks
may also be used to store other kinds of data.
Container Chunk
Cycling ’74
29.2 Specification of the JXF Format 125
Matrix Chunk
chunk ID JIT_BIN_CHUNK_MATRIX ('MTRX')
chunk size 32-bit int
offset 32-bit int
type 4-char
planecount 32-bit int
dimcount 32-bit int
dim Array of 32-bit ints that contain the dimensions
data
The data offset of the matrix chunk represents the offset, in bytes, from the beginning of the chunk to the beginning
of the data portion of the chunk. The type is one of CHAR, LONG, FL32 and FL64. The dim array contains dimcount
elements, each of which is a 32-bit int. The data portion consists of the cells of the matrix written out one at a time in
row-major order. Planar data is multiplexed in each cell. For example, a 3-plane 2 by 2 matrix would be written out in
the following order:
The various chunks discussed above can be represented by the C structs listed below:
typedef struct _jit_bin_chunk_container
{
ulong ckid; //’FORM’
long cksize; //filesize
ulong formtype; //’JIT!’
} t_jit_bin_chunk_container;
typedef struct _jit_bin_chunk_format_version
{
ulong ckid; //’FVER’
long cksize; //12
ulong vers; //timestamp
} t_jit_bin_chunk_format_version;
typedef struct _jit_bin_chunk_matrix
{
ulong ckid; //’MTRX’
long cksize; //varies(should be equal to
//24+(4*dimcount)+(typesize*planecount*totalpoints))
long offset; //data offset(should be equal to 24+(4*dimcount))
ulong type; //’CHAR’,’LONG’,’FL32’,’FL64’
long planecount;
long dimcount;
long dim[1];
} t_jit_bin_chunk_matrix;
Cycling ’74
126 JXF File Specification
Cycling ’74
Chapter 30
This appendix describes the format of the data sent by a jit.net.send object.
The object attempts to form a TCP connection with a host at the IP and port specified by the object's attributes. Any
program wishing to receive data will therefore have to set itself up as a host and listen for incoming TCP connections.
Once a connection is formed, data can be sent. Data is sent as a stream of chunks. The first thing received will be a
chunk header. It consists of a 32-bit chunk ID and a 32-bit int representing the size of the next chunk to come. The
chunk ID can be one of the following 4-char symbols, depending on what kind of packet it is:
#define JIT_MATRIX_PACKET_ID ’JMTX’
#define JIT_MATRIX_LATENCY_PACKET_ID ’JMLP’
#define JIT_MESSAGE_PACKET_ID ’JMMP’
If the chunk is a matrix packet, the next data received will be a header of 288 bytes with the following contents:
id 'JMTX'
Size 288 (32-bit int, size of this header)
Planecount 32-bit int
Type 32-bit int, 0 for char, 1 for long, 2 for float32, 3 for float64
Dimcount 32-bit int
Dim Array of 32 32-bit ints
Dimstride Array of 32 32-bit ints
Datasize 32-bit int, size of the data buffer to come
Time 64-bit double precision float
Cycling ’74
128 Jitter Networking Specification
t_int32 dim[JIT_MATRIX_MAX_DIMCOUNT];
t_int32 dimstride[JIT_MATRIX_MAX_DIMCOUNT];
t_int32 datasize;
double time;
} t_jit_net_packet_matrix;
Following this header the next data received will be the matrix data, the size of which was passed in the above header.
When using the data, please note the dimstrides transmitted in the header.
The time field in the above header will be set to the time of transmission from the sending computer. jit.net.send expects
the server to respond by sending back timing data of its own – it uses this data to estimate the transmission latency. The
exact data in the latency chunk that jit.net.send expects to receive is the following:
id 'JMLP'
client_time_original 64-bit double, the time value received in the matrix header packet
server_time_before_data 64-bit double, the time on the server when the packet header is received
server_time_after_data 64-bit double, the time on the server after the packet has been processed and is in use
The difference between the server time before and server time after processing the data represents the time it takes the
server to mobilize the data after it has been received. jit.net.send will send and expects to receive time in milliseconds.
When this timing information is received by the transmitting computer, it notes its current time, calculates the round trip
time and then estimates the latency as half the round trip time plus half of the server processing time. This estimate is
accurate if the time of flight from A to B is the same as the time of flight from B to A, but network topology can be very
complicated, and often the route from A to B is not the reverse of the route from B to A. In simple situations, such as a
direct connection between two computers or a small LAN, the estimate should be reasonably accurate.
Finally, the last type of packet that can be sent is the message packet. The size of the message packet is sent in the
initial header packet. Standard A_GIMME messages (t_symbol ∗s, long ac, t_atom ∗av) are serialized starting with a
32-bit integer that contains the size of the serialized message in bytes. Following that another 32-bit integer gives the
argument count for the atoms. Following that comes the message atoms themselves, starting with the leading symbol if
it exists. Each atom is represented in memory first with a char that indicates what type of atom it is: 's' for symbol, 'l' for
long, and 'f' for float. For long and float atoms, the next 4 bytes contain the value of the atom; for symbol atoms a null
terminated character string follows.
Cycling ’74
129
Cycling ’74
130 Jitter Networking Specification
Cycling ’74
Chapter 31
When writing objects for Max, you typically think of creating methods which are called when a message is sent to your
object through the object's inlet.
However, your object may receive messages directly from Max rather than using the inlet.
One common example is the "assist" message, which is sent to your object when a user's mouse cursor hovers over one
of your object's inlets or outlets. If your object binds a method to the "assist" message then you will be able to customize
the message that is shown.
This appendix serves as a quick reference for messages that are commonly sent to objects by Max, should they be
implemented by the given object. Where possible, the prototypes given are actual prototypes from example objects in
the SDK rather than abstractions to assist in finding the context for these calls.
Cycling ’74
132 Appendix: Messages sent to Objects
key long uitextfield_key(t_uitextfield ∗x, t_object ∗patcherview, long keycode, long modifiers, long textchar-
acter);
keyfilter long uitextfield_keyfilter(t_uitextfield ∗x, t_object ∗patcherview, long ∗keycode, long ∗modifiers, long
∗textcharacter);
enter void uitextfield_enter(t_uitextfield ∗x);
select void uitextfield_select(t_uitextfield ∗x);
Cycling ’74
31.7 Messages for Dataview Client Objects 133
getcelltext void dbviewer_getcelltext(t_dbviewer ∗x, t_symbol ∗colname, long index, char ∗text, long
maxlen);
newpatcherview void dbviewer_newpatcherview(t_dbviewer ∗x, t_object ∗patcherview);
freepatcherview void dbviewer_freepatcherview(t_dbviewer ∗x, t_object ∗patcherview);
Cycling ’74
134 Appendix: Messages sent to Objects
Cycling ’74
Chapter 32
If you are writing user interface objects for Max, it is recommended that you provide an icon for your object.
Providing an icon will allow users to create an instance of your class from the object palette, and improve the user's
experience in other interactions with Max including the Object Defaults inspector.
To see the icons provided by Cycling '74 for objects included in Max, look in the Cycling '74/object-icons folder installed
by Max. You will find a variety of SVG (scalable vector graphics) files for the objects. The files are named with the same
name of the class (as it is defined in your ext_main() function) with which they are associated.
SVG files can be edited in a variety of software applications such as InkScape or Adobe Illustrator. You can also export
SVG files from OmniGraffle on the Mac, which is how the Max's object icons were created.
It is recommended that you distribute your object as a Max Package (see Appendix: SDK changes for Max 7). Within
this package you shoulo place your svg in the 'interfaces' folder.
Adding the svg file will make the icon available to Max for use in some ways. To make your icon appear in Max's
Object Explorer, however, you must create a quick-lookup (or qlookup) entry for your object. If you look in the Cycling
'74/interfaces folder, you should notice some files with names like "obj-qlookup.json" and "doc-qlookup.json". For your
object, you should create a similar qlookup file.
For the following example we will assume you have created an object called 'littleuifoo'. For this object we will create a
qlookup called 'littleuifoo-obj-qlookup.json'. The contents of this file will look like this:
Cycling ’74
136 Appendix: Providing Icons for UI Objects
{
"littleuifoo": {
"digest": "Little UI Object that does Foo",
"module": "max",
"category": [
"U/I"
],
"palette": {
"category": [
"Interface"
],
"action": "littleuifoo",
"pic": "littleuifoo.svg"
}
}
}
Cycling ’74
Chapter 33
While it is out of the scope of this document to cover many topics related to Jitter development, we suggest the following
resources to better inform your development.
• "The C Programming Language", Kernighan and Ritchie (Prentice Hall, 1988). ISBN: 0131103709
• "A Book on C", Kelly and Pohl (Addison Wesley, 1997). ISBN: 0201183994
• "Handbook of Image and Video Processing", A. Bovik et al. (Academic Press, 2000). ISBN: 0121197921
• "Digital Image Processing", W. K. Pratt (John Wiley and Sons, 2001). ISBN: 0471857661
• "Principles of Digital Image Synthesis", A. S. Glassner (Morgan Kaufmann, 1995). ISBN: 1558602763
Open GL:
Microsoft:
Cycling ’74
138 Appendix: Additional Resources
Cycling ’74
Chapter 34
34.1.1 Background
In Max 5 and prior versions, the signal chain for processing audio was compiled by sending all objects in the patcher a
"dsp" message. Objects responding to this message then executed their dsp method, typically adding one of the object's
perform methods to the signal chain.
In Max 6, the signal chain is compiled by first sending objects a "dsp64" message. When your object responds to this
message, you can add your 64-bit audio perform methods. If an object supports the old "dsp" message but not the
"dsp64" message, it then wraps the older 32-bit perform routine with conversion on the inputs and outputs.
This means that the 64-bit engine will work just fine with the older 32-bit objects. However, the conversion comes with
some computational expense. For the best performance your objects should support the 64-bit dsp chain natively by
implementing the "dsp64" message as explained below.
34.1.2 API
As noted, instead of the "dsp" method used by objects for Max 5 and earlier, Max 6 objects implement a "dsp64" method.
This has the same purpose as the original dsp method. One notable difference is that the signals are not passed to the
dsp64 method. This is to allow for the signal that is used to change dynamically at runtime. However, the relevant info
(samplerate, number of signals connected, etc) is passed in.
The main purpose of the dsp64 method is to call back into the audio lib to put perform methods on the dsp chain. This
is done by sending the 'dsp_add64' message to the dspchain object using object_method().
The perform routine is now of type t_perfroutine64, defined in z_dsp.h, and now has a fixed function signature. It does
take a user-defined parameter that is passed back from the call to 'dsp_add64'.
Cycling ’74
140 Appendix: Updating Externals for Max 6
The simplemsp∼ examples in the 'audio' folder of the SDK have been updated for 64-bit audio processing in Max 6.
Several projects, including the simplemsp∼ example, demonstrate how to support both 64-bit audio processing in Max
6 and 32-bit audio processing for compatibility with Max 5.
On the Macintosh platform, Max 6 made the transition from using the Carbon API to using the Cocoa API for interacting
with the Mac OS. In most cases the transition for third-party developers should be seemless. If you are operating directly
using native Carbon calls then your code will need to be updated to Cocoa using Objective-C.
The most common scenario is where you ask a patcherview for the native window handle with a call such as:
WindowRef viewWindow;
object_method(patcherview, gensym("nativewindow"), (void**)&viewWindow);
In Max 6 this will not work because the returned 'viewWindow' is not the Carbon WindowRef but is instead a Cocoa
NSWindow∗. You may update your code to use Cocoa instead of Carbon, or you may wish to transition more slowly by
continuing to use a WindowRef. Here is an example to assist in obtaining a WindowRef:
NSView *cocoa_view = NULL;
NSWindow *cocoa_window = NULL;
WindowRef carbon_window;
object_method(patcherview, gensym("nativewindow"), (void**)&cocoa_view);
if (cocoa_view) {
cocoa_window = [cocoa_view window];
if (cocoa_window) {
carbon_window = [cocoa_window windowRef];
}
}
// now you can use your carbon_window as before
Cycling ’74
Chapter 35
35.1 Background
The Max 6.0.x application binary, and the external objects and libraries it uses, are compiled for the i386 processor
architecture. This architecture uses 32-bit memory addressing, meaning that the size of a pointer is 32 bits (or 4 bytes).
Max 6.1 introduces support for the x86_64 (or x64) architecture which uses 64-bit (8 bytes) memory addressing. Among
the benefits are the ability to use more than 2 GB of memory in Max. Additionally, the size of the t_atom is 8-bytes on
x64, meaning that double-precision floating pointer numbers can be represented.
For backwards compatibility, Max 6.1 also continues to be distributed as a 32-bit application binary. On the Windows
platform the 32-bit and 64-bit applications are distributed separately, as are the external objects you create for them. On
the Mac platform a Universal Binary (or "FAT" binary) is distributed containing both the 32-bit and 64-bit versions in the
same dynamically-loaded library.
All externals on the Mac remain bundles using the ".mxo" filename extension.
32-bit externals on Windows remain DLLs using the ".mxe" filename extension.
64-bit externals on Windows are still DLLs but use a new ".mxe64" filename extension.
In addition to the change of size in a pointer, there are some additional changes for 64-bit. For example, a "long" integer
for 32-bit targets is 4 bytes on both the Mac and Windows. However, a "long" integer for 64-bit targets is 4 bytes on
Windows but 8 bytes (the size of a pointer) on the Mac!
To facilitate cross platform code that is independent of these platform differences, the Max 6.1 API defines some new
types used throughout the SDK.
Cycling ’74
142 Appendix: Updating Externals for Max 6.1 (x64 architecture)
t_int16
t_uint16
t_int32
t_uint32
t_int64
t_uint64
For new objects, you can base projects on those in the new SDK. To update existing projects you will need to make a
few changes to your project settings.
Max 6.1 on the Mac no longer uses the intermediary MaxAPI.framework for linking. Instead, the linking is handled at
runtime and the symbols are checked using special flags to the linker. To update an existing project:
2. update the .xcconfig file on which the project is based with the .xcconfig file in the new Max SDK
3. in your target's build settings find the "Other Linker Flags" and set it to "$(C74_SYM_LINKER_FLAGS)"
4. in your target's build settings find the "Architectures" and set it to "i386 x86_64"
In order to build for x64 with Visual Studio 2008, you must have the "Pro" version. The free "Express" version will not
work. The "Express" versions of Visual Studio 2010 and 2012 do work (2012 is recommended).
Due to bugs in Visual Studio 2008, it is really difficult to update an existing project. Instead, it is recommended to simply
create a new Visual Studio project based on an existing example. For Visual Studio 2008 use the "vcproj" files. For
Visual Studio 2010 and 2012 use the "vcxproj" files.
4. do a find/replace for all instances of the text "dummy" changing it to your object's name
5. open the Visual Studio project and build you can choose either "Win32" or "x64" from the platform drop-down
menu in the IDE
Cycling ’74
35.3 Changes to Code 143
35.3.1 Atoms
Any assumptions in your code about the size of a t_atom or the size of its members should be reviewed. When setting
or getting values to and from atoms you should use the types t_atom_long and t_atom_float as appropriate.
All methods which return a value must return a pointer-sized value, e.g., t_ptr_int, t_ptr_uint, t_max_err, etc.
File access in Max involves several areas subject to either required or suggested update.
A path in Max has traditionally been represented with a short int; it is recommened to now use the new t_filepath type.
File types in Max are represented using four char codes. Traditionally these have been defined using variables of type
"long", which is now problematic. This is a 4-byte type but the long on the Mac for x64 is 8-bytes. These must be
updated to use the new t_fourcc type.
One of biggest areas we've had to address is the use of the long datatype. The reason for this is that under 64bit
windows a long integer is 32 bits and under 64bit OS X (and Unix), a long is a 64 bit integer.
To assist in this process, we have a the new data types documented above. We'll distinguis these from what we are
calling a "platform long".
This platform long discrepancy can lead to all sorts of problems which are outlined with a brief statement of the problem
scenario, and our recommended fix with types:
Problem: long integers as A_LONG/A_DEFLONG method arguments (this includes your object constructors)
Solution: type your A_LONG/A_DEFLONG methods' function signatures to use the t_atom_long in place of long
Problem: long integers as A_CANT method arguments called only through object_method()
Cycling ’74
144 Appendix: Updating Externals for Max 6.1 (x64 architecture)
Solution: either redefine your A_CANT method's arguments to t_atom_long, or define your type as A_DIRECT, and
make use of the object_method_direct() macro, passing in a function prototype to the macro (also see under floating
point how this is required for anything which previously was A_CANT with floating point values). Technically many of
these will still work properly due to the nature of how integers are passed on the stack under x64, without any change,
it is still best practice.
Problem: long integers being used to store pointers as integer values either for pointer arithmetic, attributes,
or other situations.
Solution: use t_atom_long or even better t_ptr_uint (for pointer sized unsigned integer) or the actual pointer type.
Problem: long integers as four character codes for filetypes (t_fourcc) which applies to locatefile_extended and
path functions and friends
Solution: Use t_foucc inplace of long, for anywhere you are using filetype codes.
Problem: long integers as return values for functions called via object_method()
Solution: These should always return a t_atom_long or other pointer sized integer
Problem: long integers passed as pointers into functions like dictionary_getlong() which are now prototyped
to take a t_atom_long ∗
Solution: Use a t_atom_long value, and pass a pointer to it. A cast from a platform long ∗to a t_atom_long ∗ is not safe.
Problem: long integers for performing bitwise manipulation of 32bit floating point values including byteswap-
ping
There are many cases where it is safe to use long integers, and we have continued to use them in our code. Below are
the scenarios where they are okay and in several cases required. This might provide some confusion at some points,
but hopefully it makes the porting process a little bit easier, allowing more code to remain unchanged.
• Attributes defined as _sym_long should remain a platform long. If you need to have a t_atom_long attribute, you
will need to use the new atom_long attribute type. This is probably the most confusing aspect of porting to 64bit
and a very real ambiguity of the word "long". Unfortunately, having to balance the difficulties of porting with the
clarity of API, this is something we felt necessary to do.
• Attribute getters/setters should still use the long type for ac. this is especially important for getters which are
passed a pointer to a platform long in the ac value.
• A_GIMME methods may still use the long type for ac without issues
For A_FLOAT/A_DEFFLOAT function signatures, you should always use double as is currently recommended in the
Max SDK. You should not use the new t_atom_float dataype. (this includes your object constructors)
For A_CANT functions with floating point arguments that currently use object_method(). You will need to use
object_method_direct() or pass in pointers to the floating point values (which is safe as it is a pointer sized inte-
ger). It is no longer possible to pass floats through object_method() or the many functions like it (linklist_methodall(),
hashtab_methodall(), etc.)
Attributes are already defined in terms of their bitsize float32 or float 64. If you wish for your attribute to make use of
the new atom support for double preceision. You will want to change your struct definition, as well as your attribute
constructor to be a double (_sym_float64). There isn't currently a t_atom_float attribute type like we've added for long.
Cycling ’74
35.5 Additional Miscellaneous Changes 145
like Byte/Boolean/Point/etc
The old 32-bit 'dsp' (and perform) methods are no longer supported as of Max 6.1. They must be updated as per
Updating MSP Externals for 64-bit Audio Processing .
• http://www.viva64.com/en/a/0004/
• https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/←-
Cocoa64BitGuide/64BitChangesCocoa/64BitChangesCocoa.html
• https://developer.apple.com/library/mac/#documentation/Carbon/Conceptual/←-
Carbon64BitGuide/OtherAPIChanges/OtherAPIChanges.html
• http://msdn.microsoft.com/en-us/magazine/cc300794.aspx
Cycling ’74
146 Appendix: Updating Externals for Max 6.1 (x64 architecture)
Cycling ’74
Chapter 36
Prior to Max 7 the entry point for externals was the main() function exported from the dynamic library you create.
Beginning with Max 7 the entry point for externals is called ext_main(). This addresses compatibility problems with
various newer compilers and frees us from the constraints enforced for main() as the standard entry point for programs.
Objects that do no not define ext_main() will still be loaded using the older main(). Support for ext_main() is also present
in Max 6.1.9.
Max 7 introduces the concept of styles which determine the appearance of UI objects. For attributes of your UI object to
map to colors or attributes of a style you need to add the required attribute properties in your class definition.
Cycling ’74
148 Appendix: SDK changes for Max 7
Cycling ’74
Chapter 37
Max 8 introduces the concept of MC for multi-voice/multi-channel signal processing. See the chapter on MC for more
information.
Cycling ’74
150 Appendix: SDK changes for Max 8
Cycling ’74
Chapter 38
Module Documentation
38.1 Attributes
An attribute of an object is a setting or property that tells the object how to do its job.
Data Structures
• struct t_attr
Common attr struct.
Macros
Cycling ’74
152 Module Documentation
Cycling ’74
38.1 Attributes 153
Cycling ’74
154 Module Documentation
Cycling ’74
38.1 Attributes 155
Cycling ’74
156 Module Documentation
Enumerations
• enum e_max_attrflags {
ATTR_FLAGS_NONE , ATTR_GET_OPAQUE , ATTR_SET_OPAQUE , ATTR_GET_OPAQUE_USER ,
ATTR_SET_OPAQUE_USER , ATTR_GET_DEFER , ATTR_GET_USURP , ATTR_GET_DEFER_LOW ,
ATTR_GET_USURP_LOW , ATTR_SET_DEFER , ATTR_SET_USURP , ATTR_SET_DEFER_LOW ,
ATTR_SET_USURP_LOW , ATTR_IS_JBOXATTR , ATTR_DIRTY }
Attribute flags.
Functions
Cycling ’74
38.1 Attributes 157
• t_object ∗ attr_offset_array_new (C74_CONST char ∗name, t_symbol ∗type, long size, long flags, method mget,
method mset, long offsetcount, long offset)
Create a new attribute.
• t_atom_long object_attr_getlong (void ∗x, t_symbol ∗s)
Retrieves the value of an attribute, given its parent object and name.
• t_max_err object_attr_setlong (void ∗x, t_symbol ∗s, t_atom_long c)
Sets the value of an attribute, given its parent object and name.
• t_atom_float object_attr_getfloat (void ∗x, t_symbol ∗s)
Retrieves the value of an attribute, given its parent object and name.
• t_max_err object_attr_setfloat (void ∗x, t_symbol ∗s, t_atom_float c)
Sets the value of an attribute, given its parent object and name.
• t_symbol ∗ object_attr_getsym (void ∗x, t_symbol ∗s)
Retrieves the value of an attribute, given its parent object and name.
• t_max_err object_attr_setsym (void ∗x, t_symbol ∗s, t_symbol ∗c)
Sets the value of an attribute, given its parent object and name.
• long object_attr_getlong_array (void ∗x, t_symbol ∗s, long max, t_atom_long ∗vals)
Retrieves the value of an attribute, given its parent object and name.
• t_max_err object_attr_setlong_array (void ∗x, t_symbol ∗s, long count, t_atom_long ∗vals)
Sets the value of an attribute, given its parent object and name.
• long object_attr_getchar_array (void ∗x, t_symbol ∗s, long max, t_uint8 ∗vals)
Retrieves the value of an attribute, given its parent object and name.
• t_max_err object_attr_setchar_array (void ∗x, t_symbol ∗s, long count, C74_CONST t_uint8 ∗vals)
Sets the value of an attribute, given its parent object and name.
• long object_attr_getfloat_array (void ∗x, t_symbol ∗s, long max, float ∗vals)
Retrieves the value of an attribute, given its parent object and name.
• t_max_err object_attr_setfloat_array (void ∗x, t_symbol ∗s, long count, float ∗vals)
Sets the value of an attribute, given its parent object and name.
• long object_attr_getdouble_array (void ∗x, t_symbol ∗s, long max, double ∗vals)
Retrieves the value of an attribute, given its parent object and name.
• t_max_err object_attr_setdouble_array (void ∗x, t_symbol ∗s, long count, double ∗vals)
Sets the value of an attribute, given its parent object and name.
• long object_attr_getsym_array (void ∗x, t_symbol ∗s, long max, t_symbol ∗∗vals)
Retrieves the value of an attribute, given its parent object and name.
• t_max_err object_attr_setsym_array (void ∗x, t_symbol ∗s, long count, t_symbol ∗∗vals)
Sets the value of an attribute, given its parent object and name.
• t_max_err attr_addfilterset_clip (void ∗x, double min, double max, long usemin, long usemax)
Attaches a clip filter to an attribute.
• t_max_err attr_addfilterset_clip_scale (void ∗x, double scale, double min, double max, long usemin, long usemax)
Attaches a clip/scale filter to an attribute.
• t_max_err attr_addfilterget_clip (void ∗x, double min, double max, long usemin, long usemax)
Attaches a clip filter to an attribute.
• t_max_err attr_addfilterget_clip_scale (void ∗x, double scale, double min, double max, long usemin, long usemax)
Attaches a clip/scale filter to an attribute.
• t_max_err attr_addfilter_clip (void ∗x, double min, double max, long usemin, long usemax)
Attaches a clip filter to an attribute.
• t_max_err attr_addfilter_clip_scale (void ∗x, double scale, double min, double max, long usemin, long usemax)
Attaches a clip/scale filter to an attribute.
Cycling ’74
158 Module Documentation
An attribute of an object is a setting or property that tells the object how to do its job.
For example, the metro object has an interval attribute that tells it how fast to run.
Attributes are similar to methods, except that the attributes have a state. Attributes are themselves objects, and they
share a common interface for getting and setting values.
Cycling ’74
38.1 Attributes 159
An attribute is most typically added to the class definition of another object during it's class initialization or ext_main()
function. Most typically, this attribute's value will be stored in an instance's struct, and thus it will serve as a property of
that instance of the object.
Attributes can, however, be declared as 'class static'. This means that the property is shared by all instances of the
class, and the value is stored as a shared (static) variable.
Additionally, Max 5 has introduced the notion of 'instance attributes' (also called 'object attributes'). Instance attributes
are the creation of an attribute object, and then adding it to one specific instance of another class.
Finally, because attributes themselves are Max objects they too can possess attributes. These 'attributes of attributes'
are used in Max to do things like specify a range of values for an attribute, give an attribute human friendly caption, or
determine to what category an attribute should belong in the inspector.
The easiest and most common way of working with attributes is to use the provided macros. These macros simplify the
process of creating a new attribute object, setting any attributes of the attribute, and binding it to an object class or an
object instance.
By default, Max provides standard attribute accessors. These are the functions the get or set the attribute value
in the object's struct. If you need to define a custom accessor, you can specify this information using the
CLASS_ATTR_ACCESSORS macro.
If you need to define a custom accessor, it should have a prototype and form comparable to the following custom getter:
t_max_err foo_myval_get(t_foo *x, void *attr, long *ac, t_atom **av)
{
if ((*ac)&&(*av)) {
//memory passed in, use it
} else {
//otherwise allocate memory
*ac = 1;
if (!(*av = getbytes(sizeof(t_atom)*(*ac)))) {
*ac = 0;
return MAX_ERR_OUT_OF_MEM;
}
}
atom_setfloat(*av,x->myval);
return MAX_ERR_NONE;
}
Note that getters require memory to be allocated, if there is not memory passed into the getter. Also the attr argument is
the class' attribute object and can be queried using object_method for things like the attribute flags, names, filters, etc..
If you need to define a custom accessor, it should have a prototype and form comparable to the following custom setter:
t_max_err foo_myval_set(t_foo *x, void *attr, long ac, t_atom *av)
{
if (ac&&av) {
x->myval = atom_getfloat(av);
} else {
// no args, set to zero
x->myval = 0;
}
return MAX_ERR_NONE;
}
Cycling ’74
160 Module Documentation
Although the subject of object registration and notification is covered elsewhere, it bears noting that attributes of all types
will, if registered, automatically send notifications to all attached client objects each time the attribute's value is set.
38.1.4.1 CLASS_ATTR_ACCESSORS
#define CLASS_ATTR_ACCESSORS(
c,
attrname,
getter,
setter )
If you specify a non-NULL value for the setter or getter, then the function you specify will be called to set or get the
attribute's value rather than using the built-in accessor.
Parameters
c The class pointer.
attrname The name of the attribute as a C-string.
getter An appropriate getter method as discussed in Setting and Getting Attribute Values, or NULL to use the
default getter.
setter An appropriate setter method as discussed in Setting and Getting Attribute Values, or NULL to use the
default setter.
38.1.4.2 CLASS_ATTR_ADD_FLAGS
#define CLASS_ATTR_ADD_FLAGS(
c,
attrname,
flags )
Parameters
c The class pointer.
attrname The name of the attribute as a C-string.
flags Any flags you wish to add to this attribute, as defined in e_max_attrflags.
Cycling ’74
38.1 Attributes 161
38.1.4.3 CLASS_ATTR_ALIAS
#define CLASS_ATTR_ALIAS(
c,
attrname,
aliasname )
Parameters
c The class pointer.
attrname The name of the actual attribute as a C-string.
aliasname The name of the new alias attribute.
38.1.4.4 CLASS_ATTR_ATOM
#define CLASS_ATTR_ATOM(
c,
attrname,
flags,
structname,
structmember )
Parameters
c The class pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
38.1.4.5 CLASS_ATTR_ATOM_ARRAY
#define CLASS_ATTR_ATOM_ARRAY(
c,
Cycling ’74
162 Module Documentation
attrname,
flags,
structname,
structmember,
size )
Parameters
c The class pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
size The number of items in the t_atom array.
38.1.4.6 CLASS_ATTR_ATOM_LONG
#define CLASS_ATTR_ATOM_LONG(
c,
attrname,
flags,
structname,
structmember )
Parameters
c The class pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
38.1.4.7 CLASS_ATTR_ATOM_LONG_ARRAY
#define CLASS_ATTR_ATOM_LONG_ARRAY(
c,
Cycling ’74
38.1 Attributes 163
attrname,
flags,
structname,
structmember,
size )
Parameters
c The class pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
size The number of longs in the array.
38.1.4.8 CLASS_ATTR_ATOM_VARSIZE
#define CLASS_ATTR_ATOM_VARSIZE(
c,
attrname,
flags,
structname,
structmember,
sizemember,
maxsize )
Parameters
c The class pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
sizemember The actual number of items in the t_atom array at any given moment.
maxsize The maximum number of items in the t_atom array, i.e. the number of members allocated for the
array in the struct.
Cycling ’74
164 Module Documentation
38.1.4.9 CLASS_ATTR_BASIC
#define CLASS_ATTR_BASIC(
c,
attrname,
flags )
Add a new attribute to the specified attribute to specify that it should appear in the inspector's Basic tab.
Parameters
c The class pointer.
attrname The name of the attribute as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
38.1.4.10 CLASS_ATTR_CATEGORY
#define CLASS_ATTR_CATEGORY(
c,
attrname,
flags,
parsestr )
Add a new attribute to the specified attribute to specify a category to which the attribute is assigned in the Max inspector.
Categories are represented in the inspector as tabs. If the specified category does not exist then it will be created.
Parameters
c The class pointer.
attrname The name of the attribute as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
parsestr A C-string, which will be parsed into an array of atoms to set the initial value.
38.1.4.11 CLASS_ATTR_CHAR
#define CLASS_ATTR_CHAR(
c,
attrname,
flags,
structname,
structmember )
Cycling ’74
38.1 Attributes 165
Parameters
c The class pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
38.1.4.12 CLASS_ATTR_CHAR_ARRAY
#define CLASS_ATTR_CHAR_ARRAY(
c,
attrname,
flags,
structname,
structmember,
size )
Parameters
c The class pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
size The number of chars in the array.
38.1.4.13 CLASS_ATTR_CHAR_VARSIZE
#define CLASS_ATTR_CHAR_VARSIZE(
c,
attrname,
flags,
structname,
structmember,
sizemember,
maxsize )
Cycling ’74
166 Module Documentation
Parameters
c The class pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
sizemember The actual number of items in the char array at any given moment.
maxsize The maximum number of items in the char array, i.e. the number of members allocated for the
array in the struct.
38.1.4.14 CLASS_ATTR_DEFAULT
#define CLASS_ATTR_DEFAULT(
c,
attrname,
flags,
parsestr )
The default value will be automatically set when the object is created only if your object uses a dictionary constructor
with the CLASS_FLAG_NEWDICTIONARY flag.
Parameters
c The class pointer.
attrname The name of the attribute as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
parsestr A C-string, which will be parsed into an array of atoms to set the initial value.
38.1.4.15 CLASS_ATTR_DEFAULT_PAINT
#define CLASS_ATTR_DEFAULT_PAINT(
c,
attrname,
flags,
parsestr )
Cycling ’74
38.1 Attributes 167
Parameters
c The class pointer.
attrname The name of the attribute as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
parsestr A C-string, which will be parsed into an array of atoms to set the initial value.
See also
CLASS_ATTR_DEFAULT
CLASS_ATTR_PAINT
38.1.4.16 CLASS_ATTR_DEFAULT_SAVE
#define CLASS_ATTR_DEFAULT_SAVE(
c,
attrname,
flags,
parsestr )
Parameters
c The class pointer.
attrname The name of the attribute as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
parsestr A C-string, which will be parsed into an array of atoms to set the initial value.
See also
CLASS_ATTR_DEFAULT
CLASS_ATTR_SAVE
38.1.4.17 CLASS_ATTR_DEFAULT_SAVE_PAINT
#define CLASS_ATTR_DEFAULT_SAVE_PAINT(
c,
attrname,
flags,
parsestr )
Cycling ’74
168 Module Documentation
Parameters
c The class pointer.
attrname The name of the attribute as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
parsestr A C-string, which will be parsed into an array of atoms to set the initial value.
See also
CLASS_ATTR_DEFAULT
CLASS_ATTR_PAINT
CLASS_ATTR_SAVE
38.1.4.18 CLASS_ATTR_DEFAULTNAME
#define CLASS_ATTR_DEFAULTNAME(
c,
attrname,
flags,
parsestr )
Add a new attribute to the specified attribute to specify a default value, based on Max's Object Defaults.
If a value is present in Max's Object Defaults, then that value will be used as the default value. Otherwise, use the default
value specified here. The default value will be automatically set when the object is created only if your object uses a
dictionary constructor with the CLASS_FLAG_NEWDICTIONARY flag.
Parameters
c The class pointer.
attrname The name of the attribute as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
parsestr A C-string, which will be parsed into an array of atoms to set the initial value.
38.1.4.19 CLASS_ATTR_DEFAULTNAME_PAINT
#define CLASS_ATTR_DEFAULTNAME_PAINT(
c,
attrname,
flags,
parsestr )
Cycling ’74
38.1 Attributes 169
Cycling ’74
170 Module Documentation
Parameters
c The class pointer.
attrname The name of the attribute as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
parsestr A C-string, which will be parsed into an array of atoms to set the initial value.
See also
CLASS_ATTR_DEFAULTNAME
CLASS_ATTR_PAINT
CLASS_ATTR_SAVE
38.1.4.20 CLASS_ATTR_DEFAULTNAME_SAVE
#define CLASS_ATTR_DEFAULTNAME_SAVE(
c,
attrname,
flags,
parsestr )
Parameters
c The class pointer.
attrname The name of the attribute as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
parsestr A C-string, which will be parsed into an array of atoms to set the initial value.
See also
CLASS_ATTR_DEFAULTNAME
CLASS_ATTR_SAVE
38.1.4.21 CLASS_ATTR_DEFAULTNAME_SAVE_PAINT
#define CLASS_ATTR_DEFAULTNAME_SAVE_PAINT(
c,
attrname,
Cycling ’74
38.1 Attributes 171
flags,
parsestr )
Cycling ’74
172 Module Documentation
Parameters
c The class pointer.
attrname The name of the attribute as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
parsestr A C-string, which will be parsed into an array of atoms to set the initial value.
See also
CLASS_ATTR_DEFAULTNAME
CLASS_ATTR_PAINT
CLASS_ATTR_SAVE
38.1.4.22 CLASS_ATTR_DOUBLE
#define CLASS_ATTR_DOUBLE(
c,
attrname,
flags,
structname,
structmember )
Parameters
c The class pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
38.1.4.23 CLASS_ATTR_DOUBLE_ARRAY
#define CLASS_ATTR_DOUBLE_ARRAY(
c,
attrname,
flags,
structname,
Cycling ’74
38.1 Attributes 173
structmember,
size )
Cycling ’74
174 Module Documentation
Parameters
c The class pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
size The number of doubles in the array.
38.1.4.24 CLASS_ATTR_DOUBLE_VARSIZE
#define CLASS_ATTR_DOUBLE_VARSIZE(
c,
attrname,
flags,
structname,
structmember,
sizemember,
maxsize )
Parameters
c The class pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
sizemember The actual number of items in the double array at any given moment.
maxsize The maximum number of items in the double array, i.e. the number of members allocated for the
array in the struct.
38.1.4.25 CLASS_ATTR_ENUM
#define CLASS_ATTR_ENUM(
c,
attrname,
flags,
parsestr )
Add a new attribute to the specified attribute to specify a list of choices to display in a menu for the Max inspector.
Cycling ’74
38.1 Attributes 175
Parameters
c The class pointer.
attrname The name of the attribute as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
parsestr A C-string, which will be parsed into an array of atoms to set the initial value.
Remarks
See also
CLASS_ATTR_ENUMINDEX
38.1.4.26 CLASS_ATTR_ENUMINDEX
#define CLASS_ATTR_ENUMINDEX(
c,
attrname,
flags,
parsestr )
Add a new attribute to the specified attribute to specify a list of choices to display in a menu for the Max inspector.
Parameters
c The class pointer.
attrname The name of the attribute as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
parsestr A C-string, which will be parsed into an array of atoms to set the initial value.
Remarks
See also
CLASS_ATTR_ENUM
Cycling ’74
176 Module Documentation
38.1.4.27 CLASS_ATTR_FILTER_CLIP
#define CLASS_ATTR_FILTER_CLIP(
c,
attrname,
minval,
maxval )
Add a filter to the attribute to limit both the lower and upper bounds of a value.
Parameters
c The class pointer.
attrname The name of the attribute as a C-string.
minval The maximum acceptable value to which the attribute will be limited.
maxval The maximum acceptable value to which the attribute will be limited.
See also
38.1.4.28 CLASS_ATTR_FILTER_MAX
#define CLASS_ATTR_FILTER_MAX(
c,
attrname,
maxval )
Parameters
c The class pointer.
attrname The name of the attribute as a C-string.
maxval The maximum acceptable value to which the attribute will be limited.
See also
CLASS_ATTR_FILTER_MIN
CLASS_ATTR_FILTER_CLIP
CLASS_ATTR_MAX
Cycling ’74
38.1 Attributes 177
38.1.4.29 CLASS_ATTR_FILTER_MIN
#define CLASS_ATTR_FILTER_MIN(
c,
attrname,
minval )
Parameters
c The class pointer.
attrname The name of the attribute as a C-string.
minval The minimum acceptable value to which the attribute will be limited.
See also
CLASS_ATTR_FILTER_MAX
CLASS_ATTR_FILTER_CLIP
CLASS_ATTR_MIN
38.1.4.30 CLASS_ATTR_FLOAT
#define CLASS_ATTR_FLOAT(
c,
attrname,
flags,
structname,
structmember )
Parameters
c The class pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
Cycling ’74
178 Module Documentation
38.1.4.31 CLASS_ATTR_FLOAT_ARRAY
#define CLASS_ATTR_FLOAT_ARRAY(
c,
attrname,
flags,
structname,
structmember,
size )
Parameters
c The class pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
size The number of floats in the array.
38.1.4.32 CLASS_ATTR_FLOAT_VARSIZE
#define CLASS_ATTR_FLOAT_VARSIZE(
c,
attrname,
flags,
structname,
structmember,
sizemember,
maxsize )
Parameters
c The class pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
sizemember The actual number of items in the float array at any given moment.
maxsize The maximum number of items in the float array, i.e. the number of members allocated for the
Cycling ’74
array in the struct.
38.1 Attributes 179
38.1.4.33 CLASS_ATTR_INT32
#define CLASS_ATTR_INT32(
c,
attrname,
flags,
structname,
structmember )
Parameters
c The class pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
38.1.4.34 CLASS_ATTR_INTRODUCED
#define CLASS_ATTR_INTRODUCED(
c,
attrname,
flags,
versionstr )
Add a new attribute to the specified attribute to indicate in which version the attribute was introduced.
Parameters
c The class pointer.
attrname The name of the attribute as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
versionstr A C-string, which will be parsed set the version number (e.g. "7.0.0").
38.1.4.35 CLASS_ATTR_INVISIBLE
#define CLASS_ATTR_INVISIBLE(
Cycling ’74
180 Module Documentation
c,
attrname,
flags )
Add a new attribute to the specified attribute to flag an attribute as invisible to the Max inspector.
Parameters
c The class pointer.
attrname The name of the attribute as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
38.1.4.36 CLASS_ATTR_LABEL
#define CLASS_ATTR_LABEL(
c,
attrname,
flags,
labelstr )
Add a new attribute to the specified attribute to specify an a human-friendly label for the Max inspector.
Parameters
c The class pointer.
attrname The name of the attribute as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
labelstr A C-string, which will be parsed into an array of atoms to set the initial value.
38.1.4.37 CLASS_ATTR_LEGACYDEFAULT
#define CLASS_ATTR_LEGACYDEFAULT(
c,
legacyattrname,
newattrname,
flags,
parsestr )
Add a new attribute to the specified attribute to specify a legacy default value.
The default value will be automatically set when the object is created only if your object uses a dictionary constructor
with the CLASS_FLAG_NEWDICTIONARY flag.
Cycling ’74
38.1 Attributes 181
Parameters
c The class pointer.
legacyattrname The name of the attribute.
newattrname The name of the attribute.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
parsestr A C-string, which will be parsed into an array of atoms to set the legacy value, used by
jbox_processlegacydefaults()
38.1.4.38 CLASS_ATTR_LONG
#define CLASS_ATTR_LONG(
c,
attrname,
flags,
structname,
structmember )
Parameters
c The class pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
38.1.4.39 CLASS_ATTR_LONG_ARRAY
#define CLASS_ATTR_LONG_ARRAY(
c,
attrname,
flags,
structname,
structmember,
size )
Cycling ’74
182 Module Documentation
Parameters
c The class pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
size The number of longs in the array.
38.1.4.40 CLASS_ATTR_LONG_VARSIZE
#define CLASS_ATTR_LONG_VARSIZE(
c,
attrname,
flags,
structname,
structmember,
sizemember,
maxsize )
Parameters
c The class pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
sizemember The actual number of items in the long array at any given moment.
maxsize The maximum number of items in the long array, i.e. the number of members allocated for the
array in the struct.
38.1.4.41 CLASS_ATTR_MAX
#define CLASS_ATTR_MAX(
c,
attrname,
flags,
parsestr )
Cycling ’74
38.1 Attributes 183
Parameters
c The class pointer.
attrname The name of the attribute as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
parsestr A C-string, which will be parsed into an array of atoms to set the initial value.
See also
CLASS_ATTR_MIN
CLASS_ATTR_FILTER_MAX
CLASS_ATTR_FILTER_CLIP
38.1.4.42 CLASS_ATTR_MIN
#define CLASS_ATTR_MIN(
c,
attrname,
flags,
parsestr )
Parameters
c The class pointer.
attrname The name of the attribute as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
parsestr A C-string, which will be parsed into an array of atoms to set the initial value.
See also
CLASS_ATTR_MAX
CLASS_ATTR_FILTER_MAX
CLASS_ATTR_FILTER_CLIP
Cycling ’74
184 Module Documentation
38.1.4.43 CLASS_ATTR_OBJ
#define CLASS_ATTR_OBJ(
c,
attrname,
flags,
structname,
structmember )
Parameters
c The class pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
38.1.4.44 CLASS_ATTR_OBJ_ARRAY
#define CLASS_ATTR_OBJ_ARRAY(
c,
attrname,
flags,
structname,
structmember,
size )
Parameters
c The class pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
size The number of items in the t_object∗ array.
Cycling ’74
38.1 Attributes 185
38.1.4.45 CLASS_ATTR_OBJ_VARSIZE
#define CLASS_ATTR_OBJ_VARSIZE(
c,
attrname,
flags,
structname,
structmember,
sizemember,
maxsize )
Parameters
c The class pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
sizemember The actual number of items in the t_object∗ array at any given moment.
maxsize The maximum number of items in the t_object∗ array, i.e. the number of members allocated for the
array in the struct.
38.1.4.46 CLASS_ATTR_OBSOLETE
#define CLASS_ATTR_OBSOLETE(
c,
attrname,
flags )
Parameters
c The class pointer.
attrname The name of the attribute as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
38.1.4.47 CLASS_ATTR_OFFSET_DUMMY
#define CLASS_ATTR_OFFSET_DUMMY(
Cycling ’74
186 Module Documentation
c,
attrname,
flags,
typesym )
Create an attribute that does not store its data in the object struct.
NB: if you use this you must have a custom getter/setter or not ever get/set. Perhaps we should rewrite this using a
generic attribute_new rather than attr_offset_new?
Parameters
c The class pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
typesym The type the getter and setter would expect: _sym_char, _sym_long, _sym_atom_long, _sym_float32,
_sym_float64, _sym_symbol, _sym_atom, etc
38.1.4.48 CLASS_ATTR_ORDER
#define CLASS_ATTR_ORDER(
c,
attrname,
flags,
parsestr )
Add a new attribute to the specified attribute to specify a default order in which to list attributes.
Parameters
c The class pointer.
attrname The name of the attribute as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
parsestr A C-string, which will be parsed into an array of atoms to set the initial value.
Remarks
A value of zero indicates that there is no ordering. Ordering values begin at 1. For example:
CLASS_ATTR_ORDER(c, "firstattr", 0, "1");
CLASS_ATTR_ORDER(c, "secondattr", 0, "2");
CLASS_ATTR_ORDER(c, "thirdattr", 0, "3");
Cycling ’74
38.1 Attributes 187
38.1.4.49 CLASS_ATTR_PAINT
#define CLASS_ATTR_PAINT(
c,
attrname,
flags )
Add a new attribute indicating that any changes to the specified attribute will trigger a call to the object's paint method.
Parameters
c The class pointer.
attrname The name of the attribute as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
38.1.4.50 CLASS_ATTR_REMOVE_FLAGS
#define CLASS_ATTR_REMOVE_FLAGS(
c,
attrname,
flags )
Parameters
c The class pointer.
attrname The name of the attribute as a C-string.
flags Any flags you wish to remove from this attribute, as defined in e_max_attrflags.
38.1.4.51 CLASS_ATTR_RENAMED
#define CLASS_ATTR_RENAMED(
c,
oldname,
newname,
flags )
Cycling ’74
188 Module Documentation
Parameters
c The class pointer.
oldname The name of the old attribute as a C-string.
newname The name of the new attribute as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
38.1.4.52 CLASS_ATTR_RGBA
#define CLASS_ATTR_RGBA(
c,
attrname,
flags,
structname,
structmember )
Parameters
c The class pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
38.1.4.53 CLASS_ATTR_SAVE
#define CLASS_ATTR_SAVE(
c,
attrname,
flags )
Add a new attribute to the specified attribute to indicate that the specified attribute should be saved with the patcher.
Parameters
c The class pointer.
attrname The name of the attribute as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
Cycling ’74
38.1 Attributes 189
38.1.4.54 CLASS_ATTR_SELFSAVE
#define CLASS_ATTR_SELFSAVE(
c,
attrname,
flags )
Add a new attribute to the specified attribute to indicate that it is saved by the object (so it does not appear in italics in
the inspector).
Parameters
c The class pointer.
attrname The name of the attribute as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
38.1.4.55 CLASS_ATTR_STYLE
#define CLASS_ATTR_STYLE(
c,
attrname,
flags,
parsestr )
Add a new attribute to the specified attribute to specify an editor style for the Max inspector.
• "enum" : a menu of available choices, whose symbol will be passed upon selection
• "enumindex" : a menu of available choices, whose index will be passed upon selection
Cycling ’74
190 Module Documentation
Parameters
c The class pointer.
attrname The name of the attribute as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
parsestr A C-string, which will be parsed into an array of atoms to set the initial value.
38.1.4.56 CLASS_ATTR_STYLE_LABEL
#define CLASS_ATTR_STYLE_LABEL(
c,
attrname,
flags,
stylestr,
labelstr )
Parameters
c The class pointer.
attrname The name of the attribute as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
stylestr A C-string that names the style for the attribute. See CLASS_ATTR_STYLE for the available styles.
labelstr A C-string that names the category to which the attribute is assigned in the inspector.
See also
CLASS_ATTR_STYLE
CLASS_ATTR_LABEL
38.1.4.57 CLASS_ATTR_SYM
#define CLASS_ATTR_SYM(
c,
attrname,
flags,
structname,
structmember )
Cycling ’74
38.1 Attributes 191
Parameters
c The class pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
38.1.4.58 CLASS_ATTR_SYM_ARRAY
#define CLASS_ATTR_SYM_ARRAY(
c,
attrname,
flags,
structname,
structmember,
size )
Parameters
c The class pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
size The number of items in the t_symbol∗ array.
38.1.4.59 CLASS_ATTR_SYM_VARSIZE
#define CLASS_ATTR_SYM_VARSIZE(
c,
attrname,
flags,
structname,
structmember,
sizemember,
maxsize )
Cycling ’74
192 Module Documentation
Parameters
c The class pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
sizemember The actual number of items in the t_symbol∗ array at any given moment.
maxsize The maximum number of items in the t_symbol∗ array, i.e. the number of members allocated for
the array in the struct.
38.1.4.60 CLASS_METHOD_ATTR_PARSE
#define CLASS_METHOD_ATTR_PARSE(
c,
methodname,
attrname,
type,
flags,
parsestring )
Parameters
c The class pointer.
methodname The name of the existing method as a C-string.
attrname The name of the attribute to add as a C-string.
type The datatype of the attribute to be added.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
parsestring A C-string, which will be parsed into an array of atoms to set the initial value.
Remarks
38.1.4.61 CLASS_METHOD_INTRODUCED
#define CLASS_METHOD_INTRODUCED(
c,
Cycling ’74
38.1 Attributes 193
methodname,
flags,
versionstr )
Add a new attribute to the specified method to indicate in which version the method was introduced.
Parameters
c The class pointer.
methodname The name of the method as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
versionstr A C-string, which will be parsed set the version number (e.g. "7.0.0").
38.1.4.62 CLASS_METHOD_OBSOLETE
#define CLASS_METHOD_OBSOLETE(
c,
methodname,
flags )
Parameters
c The class pointer.
methodname The name of the method as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
38.1.4.63 CLASS_METHOD_RENAMED
#define CLASS_METHOD_RENAMED(
c,
oldname,
newname,
flags )
Parameters
c The class pointer.
oldname The name of the old method as a C-string.
newname The name of the new method as a C-string.
flags
Cycling ’74
Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
194 Module Documentation
38.1.4.64 CLASS_STICKY_ATTR
#define CLASS_STICKY_ATTR(
c,
name,
flags,
parsestr )
Parameters
c The class pointer.
name The name of the new attribute to create as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
parsestr A C-string, which will be parsed into an array of atoms to set the initial value.
Remarks
The most common use of CLASS_STICKY_ATTR is for creating multiple attributes with the same category, as in
this example:
CLASS_STICKY_ATTR(c, "category", 0, "Foo");
CLASS_ATTR_DOUBLE(c, "bar", 0, t_myobject, x_bar);
CLASS_ATTR_LABEL(c, "bar", 0, "A Bar");
CLASS_ATTR_CHAR(c, "switch", 0, t_myobject, x_switch);
CLASS_ATTR_STYLE_LABEL(c, "switch", 0, "onoff", "Bar Switch");
CLASS_ATTR_DOUBLE(c, "flow", 0, t_myobject, x_flow);
CLASS_ATTR_LABEL(c, "flow", 0, "Flow Amount");
CLASS_STICKY_ATTR_CLEAR(c, "category");
See also
CLASS_STICKY_ATTR_CLEAR
38.1.4.65 CLASS_STICKY_ATTR_CLEAR
#define CLASS_STICKY_ATTR_CLEAR(
c,
name )
Cycling ’74
38.1 Attributes 195
Parameters
c The class pointer.
name The name of the sticky attribute as a C-string.
See also
CLASS_STICKY_ATTR
38.1.4.66 CLASS_STICKY_METHOD
#define CLASS_STICKY_METHOD(
c,
name,
flags,
parsestr )
Parameters
c The class pointer.
name The name of the new attribute to create as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
parsestr A C-string, which will be parsed into an array of atoms to set the initial value.
Remarks
The most common use of CLASS_STICKY_ATTR is for creating multiple attributes with the same category, as in
this example:
CLASS_STICKY_METHOD(c, "undocumented", 0, "1");
// add some methods here with class_addmethod()
// the undocumented attribute for methods means that the ref-page
// generator will ignore these methods.
CLASS_STICKY_METHOD_CLEAR(c, "undocumented");
See also
CLASS_STICKY_METHOD_CLEAR
Cycling ’74
196 Module Documentation
38.1.4.67 CLASS_STICKY_METHOD_CLEAR
#define CLASS_STICKY_METHOD_CLEAR(
c,
name )
Parameters
c The class pointer.
name The name of the sticky attribute as a C-string.
See also
CLASS_STICKY_METHOD
38.1.4.68 OBJ_ATTR_ATOM
#define OBJ_ATTR_ATOM(
x,
attrname,
flags,
val )
Parameters
x The object pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
val Pointer to the value.
38.1.4.69 OBJ_ATTR_ATOM_ARRAY
#define OBJ_ATTR_ATOM_ARRAY
Create an instance-local array-of-atoms attribute of fixed length, and add it to the object.
Cycling ’74
38.1 Attributes 197
Parameters
x The object pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
count The number of items in the t_atom array.
vals Pointer to the values.
38.1.4.70 OBJ_ATTR_CHAR
#define OBJ_ATTR_CHAR(
x,
attrname,
flags,
val )
Parameters
x The object pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
val Pointer to the value.
38.1.4.71 OBJ_ATTR_CHAR_ARRAY
#define OBJ_ATTR_CHAR_ARRAY(
x,
attrname,
flags,
count,
vals )
Create an instance-local array-of-chars attribute of fixed length, and add it to the object.
Parameters
x The object pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
count The number of items in the char array.
vals Pointer to the values.
Cycling ’74
198 Module Documentation
38.1.4.72 OBJ_ATTR_DEFAULT
#define OBJ_ATTR_DEFAULT(
x,
attrname,
flags,
parsestr )
Parameters
x The t_object instance pointer.
attrname The name of the attribute as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
parsestr A C-string, which will be parsed into an array of atoms to set the initial value.
See also
CLASS_ATTR_DEFAULT
38.1.4.73 OBJ_ATTR_DEFAULT_SAVE
#define OBJ_ATTR_DEFAULT_SAVE(
x,
attrname,
flags,
parsestr )
Parameters
x The t_object instance pointer.
attrname The name of the attribute as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
parsestr A C-string, which will be parsed into an array of atoms to set the initial value.
See also
CLASS_ATTR_DEFAULT_SAVE
Cycling ’74
38.1 Attributes 199
38.1.4.74 OBJ_ATTR_DOUBLE
#define OBJ_ATTR_DOUBLE(
x,
attrname,
flags,
val )
Parameters
x The object pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
val Pointer to the value.
38.1.4.75 OBJ_ATTR_DOUBLE_ARRAY
#define OBJ_ATTR_DOUBLE_ARRAY(
x,
attrname,
flags,
count,
vals )
Create an instance-local array-of-64bit-floats attribute of fixed length, and add it to the object.
Parameters
x The object pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
count The number of items in the double array.
vals Pointer to the values.
38.1.4.76 OBJ_ATTR_FLOAT
#define OBJ_ATTR_FLOAT(
x,
attrname,
Cycling ’74
200 Module Documentation
flags,
val )
Parameters
x The object pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
val Pointer to the value.
38.1.4.77 OBJ_ATTR_FLOAT_ARRAY
#define OBJ_ATTR_FLOAT_ARRAY(
x,
attrname,
flags,
count,
vals )
Create an instance-local array-of-32bit-floats attribute of fixed length, and add it to the object.
Parameters
x The object pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
count The number of items in the float array.
vals Pointer to the values.
38.1.4.78 OBJ_ATTR_LONG
#define OBJ_ATTR_LONG(
x,
attrname,
flags,
val )
Cycling ’74
38.1 Attributes 201
Parameters
x The object pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
val Pointer to the value.
38.1.4.79 OBJ_ATTR_LONG_ARRAY
#define OBJ_ATTR_LONG_ARRAY(
x,
attrname,
flags,
count,
vals )
Create an instance-local array-of-long-integers attribute of fixed length, and add it to the object.
Parameters
x The object pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
count The number of items in the long array.
vals Pointer to the values.
38.1.4.80 OBJ_ATTR_OBJ
#define OBJ_ATTR_OBJ(
x,
attrname,
flags,
val )
Parameters
x The object pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
val Pointer to the value.
Cycling ’74
202 Module Documentation
38.1.4.81 OBJ_ATTR_OBJ_ARRAY
#define OBJ_ATTR_OBJ_ARRAY(
x,
attrname,
flags,
count,
vals )
Create an instance-local array-of-objects attribute of fixed length, and add it to the object.
Parameters
x The object pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
count The number of items in the t_object∗ array.
vals Pointer to the values.
38.1.4.82 OBJ_ATTR_SAVE
#define OBJ_ATTR_SAVE(
x,
attrname,
flags )
Parameters
x The t_object instance pointer.
attrname The name of the attribute as a C-string.
flags Any flags you wish to declare for this new attribute, as defined in e_max_attrflags.
See also
CLASS_ATTR_SAVE
Cycling ’74
38.1 Attributes 203
38.1.4.83 OBJ_ATTR_SYM
#define OBJ_ATTR_SYM(
x,
attrname,
flags,
val )
Parameters
x The object pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
val Pointer to the value.
38.1.4.84 OBJ_ATTR_SYM_ARRAY
#define OBJ_ATTR_SYM_ARRAY(
x,
attrname,
flags,
count,
vals )
Create an instance-local array-of-symbols attribute of fixed length, and add it to the object.
Parameters
x The object pointer.
attrname The name of this attribute as a C-string.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
count The number of items in the t_symbol∗ array.
vals Pointer to the values.
38.1.4.85 STRUCT_ATTR_ATOM
#define STRUCT_ATTR_ATOM(
c,
flags,
Cycling ’74
204 Module Documentation
structname,
structmember )
The name of the attribute is automatically determined by the name of the struct member.
Parameters
c The class pointer.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
38.1.4.86 STRUCT_ATTR_ATOM_ARRAY
#define STRUCT_ATTR_ATOM_ARRAY(
c,
flags,
structname,
structmember,
size )
The name of the attribute is automatically determined by the name of the struct member.
Parameters
c The class pointer.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
size The number of items in the t_atom array.
38.1.4.87 STRUCT_ATTR_ATOM_LONG
#define STRUCT_ATTR_ATOM_LONG(
c,
flags,
Cycling ’74
38.1 Attributes 205
structname,
structmember )
The name of the attribute is automatically determined by the name of the struct member.
Parameters
c The class pointer.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
38.1.4.88 STRUCT_ATTR_ATOM_VARSIZE
#define STRUCT_ATTR_ATOM_VARSIZE(
c,
flags,
structname,
structmember,
sizemember,
maxsize )
The name of the attribute is automatically determined by the name of the struct member.
Parameters
c The class pointer.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
sizemember The actual number of items in the t_atom array at any given moment.
maxsize The maximum number of items in the t_atom array, i.e. the number of members allocated for the
array in the struct.
38.1.4.89 STRUCT_ATTR_CHAR
#define STRUCT_ATTR_CHAR(
c,
Cycling ’74
206 Module Documentation
flags,
structname,
structmember )
The name of the attribute is automatically determined by the name of the struct member.
Parameters
c The class pointer.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
38.1.4.90 STRUCT_ATTR_CHAR_ARRAY
#define STRUCT_ATTR_CHAR_ARRAY(
c,
flags,
structname,
structmember,
size )
The name of the attribute is automatically determined by the name of the struct member.
Parameters
c The class pointer.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
size The number of items in the char array.
38.1.4.91 STRUCT_ATTR_CHAR_VARSIZE
#define STRUCT_ATTR_CHAR_VARSIZE(
c,
Cycling ’74
38.1 Attributes 207
flags,
structname,
structmember,
sizemember,
maxsize )
The name of the attribute is automatically determined by the name of the struct member.
Parameters
c The class pointer.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
sizemember The actual number of items in the char array at any given moment.
maxsize The maximum number of items in the char array, i.e. the number of members allocated for the
array in the struct.
38.1.4.92 STRUCT_ATTR_DOUBLE
#define STRUCT_ATTR_DOUBLE(
c,
flags,
structname,
structmember )
The name of the attribute is automatically determined by the name of the struct member.
Parameters
c The class pointer.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
38.1.4.93 STRUCT_ATTR_DOUBLE_ARRAY
#define STRUCT_ATTR_DOUBLE_ARRAY(
Cycling ’74
208 Module Documentation
c,
flags,
structname,
structmember,
size )
The name of the attribute is automatically determined by the name of the struct member.
Parameters
c The class pointer.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
size The number of items in the double array.
38.1.4.94 STRUCT_ATTR_DOUBLE_VARSIZE
#define STRUCT_ATTR_DOUBLE_VARSIZE(
c,
flags,
structname,
structmember,
sizemember,
maxsize )
The name of the attribute is automatically determined by the name of the struct member.
Parameters
c The class pointer.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
sizemember The actual number of items in the double array at any given moment.
maxsize The maximum number of items in the double array, i.e. the number of members allocated for the
array in the struct.
Cycling ’74
38.1 Attributes 209
38.1.4.95 STRUCT_ATTR_FLOAT
#define STRUCT_ATTR_FLOAT(
c,
flags,
structname,
structmember )
The name of the attribute is automatically determined by the name of the struct member.
Parameters
c The class pointer.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
38.1.4.96 STRUCT_ATTR_FLOAT_ARRAY
#define STRUCT_ATTR_FLOAT_ARRAY(
c,
flags,
structname,
structmember,
size )
The name of the attribute is automatically determined by the name of the struct member.
Parameters
c The class pointer.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
size The number of items in the floats array.
Cycling ’74
210 Module Documentation
38.1.4.97 STRUCT_ATTR_FLOAT_VARSIZE
#define STRUCT_ATTR_FLOAT_VARSIZE(
c,
flags,
structname,
structmember,
sizemember,
maxsize )
The name of the attribute is automatically determined by the name of the struct member.
Parameters
c The class pointer.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
sizemember The actual number of items in the float array at any given moment.
maxsize The maximum number of items in the float array, i.e. the number of members allocated for the
array in the struct.
38.1.4.98 STRUCT_ATTR_LONG
#define STRUCT_ATTR_LONG(
c,
flags,
structname,
structmember )
The name of the attribute is automatically determined by the name of the struct member.
Parameters
c The class pointer.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
Cycling ’74
38.1 Attributes 211
38.1.4.99 STRUCT_ATTR_LONG_ARRAY
#define STRUCT_ATTR_LONG_ARRAY(
c,
flags,
structname,
structmember,
size )
The name of the attribute is automatically determined by the name of the struct member.
Parameters
c The class pointer.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
size The number of items in the long array.
38.1.4.100 STRUCT_ATTR_LONG_VARSIZE
#define STRUCT_ATTR_LONG_VARSIZE(
c,
flags,
structname,
structmember,
sizemember,
maxsize )
The name of the attribute is automatically determined by the name of the struct member.
Parameters
c The class pointer.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
sizemember The actual number of items in the long array at any given moment.
maxsize The maximum number of items in the long array, i.e. the number of members allocated for the
array in the struct.
Cycling ’74
212 Module Documentation
38.1.4.101 STRUCT_ATTR_OBJ
#define STRUCT_ATTR_OBJ(
c,
flags,
structname,
structmember )
The name of the attribute is automatically determined by the name of the struct member.
Parameters
c The class pointer.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
38.1.4.102 STRUCT_ATTR_OBJ_ARRAY
#define STRUCT_ATTR_OBJ_ARRAY(
c,
flags,
structname,
structmember,
size )
The name of the attribute is automatically determined by the name of the struct member.
Parameters
c The class pointer.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
size The number of items in the t_object∗ array.
Cycling ’74
38.1 Attributes 213
38.1.4.103 STRUCT_ATTR_OBJ_VARSIZE
#define STRUCT_ATTR_OBJ_VARSIZE(
c,
flags,
structname,
structmember,
sizemember,
maxsize )
The name of the attribute is automatically determined by the name of the struct member.
Parameters
c The class pointer.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
sizemember The actual number of items in the t_object∗ array at any given moment.
maxsize The maximum number of items in the t_object∗ array, i.e. the number of members allocated for the
array in the struct.
38.1.4.104 STRUCT_ATTR_SYM
#define STRUCT_ATTR_SYM(
c,
flags,
structname,
structmember )
The name of the attribute is automatically determined by the name of the struct member.
Parameters
c The class pointer.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
Cycling ’74
214 Module Documentation
38.1.4.105 STRUCT_ATTR_SYM_ARRAY
#define STRUCT_ATTR_SYM_ARRAY(
c,
flags,
structname,
structmember,
size )
The name of the attribute is automatically determined by the name of the struct member.
Parameters
c The class pointer.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
size The number of items in the t_symbol∗ array.
38.1.4.106 STRUCT_ATTR_SYM_VARSIZE
#define STRUCT_ATTR_SYM_VARSIZE(
c,
flags,
structname,
structmember,
sizemember,
maxsize )
The name of the attribute is automatically determined by the name of the struct member.
Parameters
c The class pointer.
flags Any flags you wish to declare for this attribute, as defined in e_max_attrflags.
structname The C identifier for the struct (containing a valid t_object header) representing an instance of this
class.
structmember The C identifier of the member in the struct that holds the value of this attribute.
sizemember The actual number of items in the t_symbol∗ array at any given moment.
maxsize The maximum number of items in the t_symbol∗ array, i.e. the number of members allocated for
the array in the struct.
Cycling ’74
38.1 Attributes 215
38.1.5.1 e_max_attrflags
enum e_max_attrflags
Attribute flags.
Remarks
To create a readonly attribute, for example, you should pass ATTR_SET_OPAQUE or ATTR_SET_OPAQUE_←-
USER as a flag when you create your attribute.
Enumerator
ATTR_FLAGS_NONE No flags.
ATTR_GET_OPAQUE The attribute cannot be queried by either max message when used inside of a
CLASS_BOX object, nor from C code.
ATTR_SET_OPAQUE The attribute cannot be set by either max message when used inside of a
CLASS_BOX object, nor from C code.
ATTR_GET_OPAQUE_USER The attribute cannot be queried by max message when used inside of a
CLASS_BOX object, but can be queried from C code.
ATTR_SET_OPAQUE_USER The attribute cannot be set by max message when used inside of a CLASS_BOX
object, but can be set from C code.
38.1.6.1 attr_addfilter_clip()
t_max_err attr_addfilter_clip (
void ∗ x,
double min,
double max,
long usemin,
long usemax )
The filter will clip any values sent to or retrieved from the attribute using the attribute's get and set functions.
Cycling ’74
216 Module Documentation
Parameters
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
38.1.6.2 attr_addfilter_clip_scale()
t_max_err attr_addfilter_clip_scale (
void ∗ x,
double scale,
double min,
double max,
long usemin,
long usemax )
The filter will clip and scale any values sent to or retrieved from the attribute using the attribute's get and set functions.
Parameters
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
Cycling ’74
38.1 Attributes 217
38.1.6.3 attr_addfilterget_clip()
t_max_err attr_addfilterget_clip (
void ∗ x,
double min,
double max,
long usemin,
long usemax )
The filter will only clip values retrieved from the attribute using the attribute's get function.
Parameters
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
38.1.6.4 attr_addfilterget_clip_scale()
t_max_err attr_addfilterget_clip_scale (
void ∗ x,
double scale,
double min,
double max,
long usemin,
long usemax )
The filter will only clip and scale values retrieved from the attribute using the attribute's get function.
Parameters
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
38.1.6.5 attr_addfilterget_proc()
t_max_err attr_addfilterget_proc (
void ∗ x,
method proc )
The filter will only be called for values retrieved from the attribute using the attribute's get function.
Parameters
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
Remarks
The filter method should be prototyped and implemented as described above for the attr_addfilterset_proc() func-
tion.
38.1.6.6 attr_addfilterset_clip()
t_max_err attr_addfilterset_clip (
void ∗ x,
double min,
double max,
long usemin,
long usemax )
The filter will only clip values sent to the attribute using the attribute's set function.
Cycling ’74
38.1 Attributes 219
Parameters
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
38.1.6.7 attr_addfilterset_clip_scale()
t_max_err attr_addfilterset_clip_scale (
void ∗ x,
double scale,
double min,
double max,
long usemin,
long usemax )
The filter will only clip and scale values sent to the attribute using the attribute's set function.
Parameters
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
Cycling ’74
220 Module Documentation
38.1.6.8 attr_addfilterset_proc()
t_max_err attr_addfilterset_proc (
void ∗ x,
method proc )
The filter will only be called for values retrieved from the attribute using the attribute's set function.
Parameters
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
Remarks
38.1.6.9 attr_args_dictionary()
void attr_args_dictionary (
t_dictionary ∗ x,
short ac,
t_atom ∗ av )
Create a dictionary of attribute-name, attribute-value pairs from an array of atoms containing an attribute definition list.
Parameters
x A dictionary instance pointer.
ac The number of atoms to parse in av.
av A pointer to the first of the array of atoms containing the attribute values.
Cycling ’74
38.1 Attributes 221
Remarks
The code example below shows the creation of a list of atoms using atom_setparse(), and then uses that list of
atoms to fill the dictionary with attr_args_dictionary().
long ac = 0;
t_atom *av = NULL;
char parsebuf[4096];
t_dictionary *d = dictionary_new();
t_atom a;
sprintf(parsebuf,"@defrect %.6f %.6f %.6f %.6f @title Untitled @presentation 0 ", r->x, r->y, r->width,
r->height);
atom_setparse(&ac, &av, parsebuf);
attr_args_dictionary(d, ac, av);
atom_setobj(&a, d);
38.1.6.10 attr_args_offset()
long attr_args_offset (
short ac,
t_atom ∗ av )
Developers can use this function to assist in the manual processing of attribute arguments, when attr_args_process()
doesn't provide the correct functionality for a particular purpose.
Parameters
Returns
This function returns an offset into the atom list, where the first attribute argument occurs. For instance, the atom
list foo bar 3.0 @mode 6 would cause attr_args_offset to return 3 (the attribute mode appears at
position 3 in the atom list).
Referenced by max_jit_attr_args_offset().
38.1.6.11 attr_args_process()
void attr_args_process (
void ∗ x,
short ac,
t_atom ∗ av )
Takes an atom list and properly set any attributes described within.
This function is typically used in an object's new method to conveniently process attribute arguments.
Cycling ’74
222 Module Documentation
Parameters
x The object whose attributes will be processed
ac The count of t_atoms in av
av An atom list
Remarks
38.1.6.12 attr_dictionary_check()
void attr_dictionary_check (
void ∗ x,
t_dictionary ∗ d )
Check that a dictionary only contains values for existing attributes of an object.
If a key in the dictionary doesn't correspond an one of the object's attributes, an error will be posted to the Max window.
Parameters
x The object instance pointer.
d The dictionary containing the attributes.
See also
attr_dictionary_process()
38.1.6.13 attr_dictionary_process()
void attr_dictionary_process (
void ∗ x,
t_dictionary ∗ d )
Cycling ’74
38.1 Attributes 223
Objects with dictionary constructors, such as UI objects, should call this method to set their attributes when an object is
created.
Parameters
x The object instance pointer.
d The dictionary containing the attributes.
See also
attr_args_process()
38.1.6.14 attr_offset_array_new()
t_object∗ attr_offset_array_new (
C74_CONST char ∗ name,
t_symbol ∗ type,
long size,
long flags,
method mget,
method mset,
long offsetcount,
long offset )
The attribute references an array of memory stored outside of itself, in the object's data structure. Attributes created
using attr_offset_array_new() can be assigned either to classes (using the class_addattr() function) or to objects (using
the object_addattr() function).
Parameters
Returns
This function returns the new attribute's object pointer if successful, or NULL if unsuccessful.
Remarks
For instance, to create a new attribute which references an array of 10 t_atoms (atm; the current number of
"active" elements in the array is held in the variable atmcount) in an object class's data structure:
t_object *attr = attr_offset_array_new("myattrarray", _sym_atom / * matches data size * /, 10 / * max * /,
0 / * no flags * /, (method)0L, (method)0L, calcoffset(t_myobject, atmcount) / * count * /,
calcoffset(t_myobject, atm) / * data * /);
Referenced by ext_main().
38.1.6.15 attr_offset_new()
t_object∗ attr_offset_new (
C74_CONST char ∗ name,
C74_CONST t_symbol ∗ type,
long flags,
C74_CONST method mget,
C74_CONST method mset,
long offset )
The attribute references memory stored outside of itself, in the object's data structure. Attributes created using
attr_offset_new() can be assigned either to classes (using the class_addattr() function) or to objects (using the
object_addattr() function).
Parameters
Cycling ’74
38.1 Attributes 225
Returns
This function returns the new attribute's object pointer if successful, or NULL if unsuccessful.
Remarks
For instance, to create a new attribute which references the value of a double variable (val) in an object class's
data structure:
t_object *attr = attr_offset_new("myattr", _sym_float64 / * matches data size * /, 0 / * no flags * /,
(method)0L, (method)0L, calcoffset(t_myobject, val));
Referenced by ext_main().
38.1.6.16 attribute_new()
t_object∗ attribute_new (
C74_CONST char ∗ name,
t_symbol ∗ type,
long flags,
method mget,
method mset )
The attribute will allocate memory and store its own data. Attributes created using attribute_new() can be assigned
either to classes (using the class_addattr() function) or to objects (using the object_addattr() function).
Parameters
Returns
This function returns the new attribute's object pointer if successful, or NULL if unsuccessful.
Cycling ’74
226 Module Documentation
Remarks
Developers wishing to define custom methods for get or set functionality need to prototype them as:
t_max_err myobject_myattr_get(t_myobject *x, void *attr, long *ac, t_atom **av);
t_max_err myobject_myattr_set(t_myobject *x, void *attr, long ac, t_atom *av);
Implementation will vary, of course, but need to follow the following basic models. Note that, as with custom
getvalueof and setvalueof methods for the object, assumptions are made throughout Max that getbytes()
has been used for memory allocation. Developers are strongly urged to do the same:
t_max_err myobject_myattr_get(t_myobject *x, void *attr, long *ac, t_atom **av)
{
if (*ac && *av)
// memory passed in; use it
else {
*ac = 1; // size of attr data
*av = (t_atom *)getbytes(sizeof(t_atom) * (*ac));
if (!(*av)) {
*ac = 0;
return MAX_ERR_OUT_OF_MEM;
}
}
atom_setlong(*av, x->some_value);
return MAX_ERR_NONE;
}
t_max_err myobject_myattr_set(t_myobject *x, void *attr, long ac, t_atom *av)
{
if (ac && av) {
x->some_value = atom_getlong(av);
}
return MAX_ERR_NONE;
}
38.1.6.17 object_addattr()
t_max_err object_addattr (
void ∗ x,
t_object ∗ attr )
Parameters
x An object to which the attribute should be attached
attr The attribute's pointer—this should be a pointer returned from attribute_new(), attr_offset_new() or
attr_offset_array_new().
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
38.1.6.18 object_attr_get()
void∗ object_attr_get (
void ∗ x,
t_symbol ∗ attrname )
Cycling ’74
38.1 Attributes 227
Parameters
Returns
38.1.6.19 object_attr_get_rect()
t_max_err object_attr_get_rect (
t_object ∗ o,
t_symbol ∗ name,
t_rect ∗ rect )
Gets the value of a t_rect attribute, given its parent object and name.
Parameters
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
38.1.6.20 object_attr_getchar_array()
long object_attr_getchar_array (
void ∗ x,
Cycling ’74
228 Module Documentation
t_symbol ∗ s,
long max,
t_uint8 ∗ vals )
Retrieves the value of an attribute, given its parent object and name.
This function uses a developer-allocated array to copy data to. Developers wishing to retrieve the value of an attribute
without pre-allocating memory should refer to the object_attr_getvalueof() function.
Parameters
Returns
Remarks
If the attribute is not of the type specified by the function, the function will attempt to coerce a valid value from the
attribute.
Referenced by jit_attr_getchar_array().
38.1.6.21 object_attr_getcolor()
t_max_err object_attr_getcolor (
t_object ∗ b,
t_symbol ∗ attrname,
t_jrgba ∗ prgba )
Gets the value of a t_jrgba attribute, given its parent object and name.
Parameters
Cycling ’74
38.1 Attributes 229
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
38.1.6.22 object_attr_getdouble_array()
long object_attr_getdouble_array (
void ∗ x,
t_symbol ∗ s,
long max,
double ∗ vals )
Retrieves the value of an attribute, given its parent object and name.
This function uses a developer-allocated array to copy data to. Developers wishing to retrieve the value of an attribute
without pre-allocating memory should refer to the object_attr_getvalueof() function.
Parameters
Returns
Remarks
If the attribute is not of the type specified by the function, the function will attempt to coerce a valid value from the
attribute.
Referenced by jit_attr_getdouble_array().
38.1.6.23 object_attr_getdump()
void object_attr_getdump (
void ∗ x,
t_symbol ∗ s,
long argc,
t_atom ∗ argv )
Forces a specified object's attribute to send its value from the object's dumpout outlet in the Max interface.
Cycling ’74
230 Module Documentation
Parameters
38.1.6.24 object_attr_getfloat()
t_atom_float object_attr_getfloat (
void ∗ x,
t_symbol ∗ s )
Retrieves the value of an attribute, given its parent object and name.
Parameters
Returns
This function returns the value of the specified attribute, if successful, or 0, if unsuccessful.
Remarks
If the attribute is not of the type specified by the function, the function will attempt to coerce a valid value from the
attribute.
Referenced by jit_attr_getfloat().
38.1.6.25 object_attr_getfloat_array()
long object_attr_getfloat_array (
void ∗ x,
t_symbol ∗ s,
long max,
float ∗ vals )
Retrieves the value of an attribute, given its parent object and name.
This function uses a developer-allocated array to copy data to. Developers wishing to retrieve the value of an attribute
without pre-allocating memory should refer to the object_attr_getvalueof() function.
Cycling ’74
38.1 Attributes 231
Parameters
Returns
Remarks
If the attribute is not of the type specified by the function, the function will attempt to coerce a valid value from the
attribute.
Referenced by jit_attr_getfloat_array().
38.1.6.26 object_attr_getjrgba()
t_max_err object_attr_getjrgba (
void ∗ ob,
t_symbol ∗ s,
t_jrgba ∗ c )
Retrieves the value of a color attribute, given its parent object and name.
Parameters
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
38.1.6.27 object_attr_getlong()
t_atom_long object_attr_getlong (
void ∗ x,
t_symbol ∗ s )
Cycling ’74
232 Module Documentation
Retrieves the value of an attribute, given its parent object and name.
Parameters
Returns
This function returns the value of the specified attribute, if successful, or 0, if unsuccessful.
Remarks
If the attribute is not of the type specified by the function, the function will attempt to coerce a valid value from the
attribute.
Referenced by jit_attr_getlong().
38.1.6.28 object_attr_getlong_array()
long object_attr_getlong_array (
void ∗ x,
t_symbol ∗ s,
long max,
t_atom_long ∗ vals )
Retrieves the value of an attribute, given its parent object and name.
This function uses a developer-allocated array to copy data to. Developers wishing to retrieve the value of an attribute
without pre-allocating memory should refer to the object_attr_getvalueof() function.
Parameters
Returns
Cycling ’74
38.1 Attributes 233
Remarks
If the attribute is not of the type specified by the function, the function will attempt to coerce a valid value from the
attribute.
Referenced by jit_attr_getlong_array().
38.1.6.29 object_attr_getpt()
t_max_err object_attr_getpt (
t_object ∗ o,
t_symbol ∗ name,
t_pt ∗ pt )
Gets the value of a t_pt attribute, given its parent object and name.
Parameters
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
38.1.6.30 object_attr_getsize()
t_max_err object_attr_getsize (
t_object ∗ o,
t_symbol ∗ name,
t_size ∗ size )
Gets the value of a t_size attribute, given its parent object and name.
Parameters
Cycling ’74
234 Module Documentation
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
38.1.6.31 object_attr_getsym()
t_symbol∗ object_attr_getsym (
void ∗ x,
t_symbol ∗ s )
Retrieves the value of an attribute, given its parent object and name.
Parameters
Returns
This function returns the value of the specified attribute, if successful, or the empty symbol (equivalent to
gensym("") or _sym_nothing), if unsuccessful.
Referenced by jit_attr_getsym().
38.1.6.32 object_attr_getsym_array()
long object_attr_getsym_array (
void ∗ x,
t_symbol ∗ s,
long max,
t_symbol ∗∗ vals )
Retrieves the value of an attribute, given its parent object and name.
This function uses a developer-allocated array to copy data to. Developers wishing to retrieve the value of an attribute
without pre-allocating memory should refer to the object_attr_getvalueof() function.
Parameters
Cycling ’74
38.1 Attributes 235
Returns
Referenced by jit_attr_getsym_array().
38.1.6.33 object_attr_method()
method object_attr_method (
void ∗ x,
t_symbol ∗ methodname,
void ∗∗ attr,
long ∗ get )
Returns the method of an attribute's get or set function, as well as a pointer to the attribute itself, from a message
name.
Parameters
Returns
38.1.6.34 object_attr_set_rect()
t_max_err object_attr_set_rect (
t_object ∗ o,
t_symbol ∗ name,
t_rect ∗ rect )
Sets the value of a t_rect attribute, given its parent object and name.
Cycling ’74
236 Module Documentation
Parameters
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
38.1.6.35 object_attr_set_xywh()
void object_attr_set_xywh (
t_object ∗ o,
t_symbol ∗ attr,
double x,
double y,
double w,
double h )
Sets the value of a t_rect attribute, given its parent object and name.
Parameters
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
38.1.6.36 object_attr_setchar_array()
t_max_err object_attr_setchar_array (
void ∗ x,
Cycling ’74
38.1 Attributes 237
t_symbol ∗ s,
long count,
C74_CONST t_uint8 ∗ vals )
Sets the value of an attribute, given its parent object and name.
The function will call the attribute's set method, using the data provided.
Parameters
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
Referenced by jit_attr_setchar_array().
38.1.6.37 object_attr_setcolor()
t_max_err object_attr_setcolor (
t_object ∗ b,
t_symbol ∗ attrname,
t_jrgba ∗ prgba )
Sets the value of a t_jrgba attribute, given its parent object and name.
Parameters
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
Cycling ’74
238 Module Documentation
38.1.6.38 object_attr_setdouble_array()
t_max_err object_attr_setdouble_array (
void ∗ x,
t_symbol ∗ s,
long count,
double ∗ vals )
Sets the value of an attribute, given its parent object and name.
The function will call the attribute's set method, using the data provided.
Parameters
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
Referenced by jit_attr_setdouble_array().
38.1.6.39 object_attr_setfloat()
t_max_err object_attr_setfloat (
void ∗ x,
t_symbol ∗ s,
t_atom_float c )
Sets the value of an attribute, given its parent object and name.
The function will call the attribute's set method, using the data provided.
Parameters
Cycling ’74
38.1 Attributes 239
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
Referenced by jit_attr_setfloat().
38.1.6.40 object_attr_setfloat_array()
t_max_err object_attr_setfloat_array (
void ∗ x,
t_symbol ∗ s,
long count,
float ∗ vals )
Sets the value of an attribute, given its parent object and name.
The function will call the attribute's set method, using the data provided.
Parameters
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
Referenced by jit_attr_setfloat_array().
38.1.6.41 object_attr_setjrgba()
t_max_err object_attr_setjrgba (
void ∗ ob,
t_symbol ∗ s,
t_jrgba ∗ c )
Sets the value of a color attribute, given its parent object and name.
The function will call the attribute's set method, using the data provided.
Cycling ’74
240 Module Documentation
Parameters
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
38.1.6.42 object_attr_setlong()
t_max_err object_attr_setlong (
void ∗ x,
t_symbol ∗ s,
t_atom_long c )
Sets the value of an attribute, given its parent object and name.
The function will call the attribute's set method, using the data provided.
Parameters
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
Referenced by jit_attr_setlong().
38.1.6.43 object_attr_setlong_array()
t_max_err object_attr_setlong_array (
void ∗ x,
t_symbol ∗ s,
long count,
t_atom_long ∗ vals )
Sets the value of an attribute, given its parent object and name.
The function will call the attribute's set method, using the data provided.
Cycling ’74
38.1 Attributes 241
Parameters
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
Referenced by jit_attr_setlong_array().
38.1.6.44 object_attr_setparse()
t_max_err object_attr_setparse (
t_object ∗ x,
t_symbol ∗ s,
C74_CONST char ∗ parsestr )
Set an attribute value with one or more atoms parsed from a C-string.
Parameters
x The object whose attribute will be set.
s The name of the attribute to set.
parsestr A C-string to parse into an array of atoms to set the attribute value.
Returns
See also
atom_setparse()
38.1.6.45 object_attr_setpt()
t_max_err object_attr_setpt (
t_object ∗ o,
t_symbol ∗ name,
t_pt ∗ pt )
Sets the value of a t_pt attribute, given its parent object and name.
Cycling ’74
242 Module Documentation
Parameters
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
38.1.6.46 object_attr_setsize()
t_max_err object_attr_setsize (
t_object ∗ o,
t_symbol ∗ name,
t_size ∗ size )
Sets the value of a t_size attribute, given its parent object and name.
Parameters
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
38.1.6.47 object_attr_setsym()
t_max_err object_attr_setsym (
void ∗ x,
t_symbol ∗ s,
t_symbol ∗ c )
Sets the value of an attribute, given its parent object and name.
The function will call the attribute's set method, using the data provided.
Cycling ’74
38.1 Attributes 243
Parameters
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
Referenced by jit_attr_setsym().
38.1.6.48 object_attr_setsym_array()
t_max_err object_attr_setsym_array (
void ∗ x,
t_symbol ∗ s,
long count,
t_symbol ∗∗ vals )
Sets the value of an attribute, given its parent object and name.
The function will call the attribute's set method, using the data provided.
Parameters
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
Referenced by jit_attr_setsym_array().
38.1.6.49 object_attr_setvalueof()
t_max_err object_attr_setvalueof (
void ∗ x,
Cycling ’74
244 Module Documentation
t_symbol ∗ s,
long argc,
t_atom ∗ argv )
Parameters
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
Referenced by jit_object_importattrs().
38.1.6.50 object_attr_usercanget()
long object_attr_usercanget (
void ∗ x,
t_symbol ∗ s )
Determines if the value of an object's attribute can be queried from the Max interface (i.e.
Parameters
Returns
This function returns 1 if the value of the attribute can be queried from the Max interface. Otherwise, it returns 0.
Referenced by jit_object_attr_usercanget().
Cycling ’74
38.1 Attributes 245
38.1.6.51 object_attr_usercanset()
long object_attr_usercanset (
void ∗ x,
t_symbol ∗ s )
Determines if an object's attribute can be set from the Max interface (i.e.
Parameters
Returns
This function returns 1 if the attribute can be set from the Max interface. Otherwise, it returns 0.
Referenced by jit_object_attr_usercanset().
38.1.6.52 object_chuckattr()
t_max_err object_chuckattr (
void ∗ x,
t_symbol ∗ attrsym )
Detach an attribute from an object that was previously attached with object_addattr().
This function will not free the attribute (use object_free() to do this manually).
Parameters
x The object to which the attribute is attached
attrsym The attribute's name
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
Cycling ’74
246 Module Documentation
38.1.6.53 object_deleteattr()
t_max_err object_deleteattr (
void ∗ x,
t_symbol ∗ attrsym )
Detach an attribute from an object that was previously attached with object_addattr().
The function will also free all memory associated with the attribute. If you only wish to detach the attribute, without
freeing it, see the object_chuckattr() function.
Parameters
x The object to which the attribute is attached
attrsym The attribute's name
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
38.1.6.54 object_new_parse()
void∗ object_new_parse (
t_symbol ∗ name_space,
t_symbol ∗ classname,
C74_CONST char ∗ parsestr )
Create a new object with one or more atoms parsed from a C-string.
Parameters
name_space The namespace in which to create the instance. Typically this is either CLASS_BOX or
CLASS_NOBOX.
classname The name of the class to instantiate.
parsestr A C-string to parse into an array of atoms to set the attribute value.
Returns
See also
atom_setparse()
object_new_typed()
Cycling ’74
38.2 Classes 247
38.2 Classes
When a user types the name of your object into an object box, Max looks for an external of this name in the searchpath
and, upon finding it, loads the bundle or dll and calls the ext_main() function.
Modules
• Old-Style Classes
• Inlets and Outlets
Routines for creating and communicating with inlets and outlets.
Data Structures
• struct t_class
The data structure for a Max class.
Macros
• #define CLASS_BOX
The namespace for all Max object classes which can be instantiated in a box, i.e.
• #define CLASS_NOBOX
A namespace for creating hidden or internal object classes which are not a direct part of the user creating patcher.
Enumerations
• enum e_max_class_flags {
CLASS_FLAG_BOX , CLASS_FLAG_POLYGLOT , CLASS_FLAG_NEWDICTIONARY , CLASS_FLAG_REGISTERED
,
CLASS_FLAG_UIOBJECT , CLASS_FLAG_ALIAS , CLASS_FLAG_MULTITOUCH , CLASS_FLAG_DO_NOT_PARSE_ATTR_ARG
,
CLASS_FLAG_DO_NOT_ZERO , CLASS_FLAG_NOATTRIBUTES , CLASS_FLAG_OWNATTRIBUTES ,
CLASS_FLAG_PARAMETER ,
CLASS_FLAG_RETYPEABLE , CLASS_FLAG_OBJECT_METHOD , CLASS_FLAG_VISUALIZER , CLASS_FLAG_USES_PROXIE
,
CLASS_FLAG_OWN_DATA , CLASS_FLAG_DYNAMICCOLOR }
Class flags.
Cycling ’74
248 Module Documentation
Functions
• BEGIN_USING_C_LINKAGE void C74_EXPORT ext_main (void ∗r)
ext_main() is the entry point for an extern to be loaded, which all externs must implement this shared/common prototype
ensures that it will be exported correctly on all platforms.
• t_class ∗ class_new (C74_CONST char ∗name, C74_CONST method mnew, C74_CONST method mfree, long
size, C74_CONST method mmenu, short type,...)
Initializes a class by informing Max of its name, instance creation and free functions, size and argument types.
• t_max_err class_free (t_class ∗c)
Frees a previously defined object class.
• t_max_err class_register (t_symbol ∗name_space, t_class ∗c)
Registers a previously defined object class.
• t_max_err class_alias (t_class ∗c, t_symbol ∗aliasname)
Registers an alias for a previously defined object class.
• t_max_err class_addmethod (t_class ∗c, C74_CONST method m, C74_CONST char ∗name,...)
Adds a method to a previously defined object class.
• t_max_err class_addattr (t_class ∗c, t_object ∗attr)
Adds an attribute to a previously defined object class.
• t_symbol ∗ class_nameget (t_class ∗c)
Retrieves the name of a class, given the class's pointer.
• t_class ∗ class_findbyname (t_symbol ∗name_space, t_symbol ∗classname)
Finds the class pointer for a class, given the class's namespace and name.
• t_class ∗ class_findbyname_casefree (t_symbol ∗name_space, t_symbol ∗classname)
Finds the class pointer for a class, given the class's namespace and name.
• t_max_err class_dumpout_wrap (t_class ∗c)
Wraps user gettable attributes with a method that gets the values and sends out dumpout outlet.
• void class_obexoffset_set (t_class ∗c, long offset)
Registers the byte-offset of the obex member of the class's data structure with the previously defined object class.
• long class_obexoffset_get (t_class ∗c)
Retrieves the byte-offset of the obex member of the class's data structure.
• long class_is_ui (t_class ∗c)
Determine if a class is a user interface object.
• t_max_err class_subclass (t_class ∗superclass, t_class ∗subclass)
Define a subclass of an existing class.
• t_object ∗ class_super_construct (t_class ∗c,...)
Call super class constructor.
When a user types the name of your object into an object box, Max looks for an external of this name in the searchpath
and, upon finding it, loads the bundle or dll and calls the ext_main() function.
Thus, Max classes are typically defined in the ext_main() function of an external.
Historically, Max classes have been defined using an API that includes functions like setup() and addmess(). This
interface is still supported, and the relevant documentation can be found in Old-Style Classes.
A more recent and more flexible interface for creating objects was introduced with Jitter 1.0 and later included directly
in Max 4.5. This newer API includes functions such as class_new() and class_addmethod(). Supporting attributes,
user interface objects, and additional new features of Max requires the use of the newer interface for definiting classes
documented on this page.
You may not mix these two styles of creating classes within an object.
Cycling ’74
38.2 Classes 249
38.2.2.1 CLASS_BOX
#define CLASS_BOX
The namespace for all Max object classes which can be instantiated in a box, i.e.
in a patcher.
38.2.3.1 e_max_class_flags
enum e_max_class_flags
Class flags.
Enumerator
38.2.4.1 class_addattr()
t_max_err class_addattr (
t_class ∗ c,
t_object ∗ attr )
Parameters
c The class pointer
attr The attribute to add. The attribute will be a pointer returned by attribute_new(), attr_offset_new() or
attr_offset_array_new().
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
38.2.4.2 class_addmethod()
t_max_err class_addmethod (
t_class ∗ c,
C74_CONST method m,
C74_CONST char ∗ name,
... )
Parameters
c The class pointer
m Function to be called when the method is invoked
name C-string defining the message (message selector)
... One or more integers specifying the arguments to the message, in the standard Max type list format (see
Chapter 3 of the Writing Externals in Max document for more information).
Cycling ’74
38.2 Classes 251
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
Remarks
The class_addmethod() function works essentially like the traditional addmess() function, adding the function
pointed to by m, to respond to the message string name in the leftmost inlet of the object.
38.2.4.3 class_alias()
t_max_err class_alias (
t_class ∗ c,
t_symbol ∗ aliasname )
Parameters
c The class pointer
aliasname A symbol who's name will become an alias for the given class
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
38.2.4.4 class_dumpout_wrap()
t_max_err class_dumpout_wrap (
t_class ∗ c )
Wraps user gettable attributes with a method that gets the values and sends out dumpout outlet.
Parameters
c The class pointer
Cycling ’74
252 Module Documentation
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
38.2.4.5 class_findbyname()
t_class∗ class_findbyname (
t_symbol ∗ name_space,
t_symbol ∗ classname )
Finds the class pointer for a class, given the class's namespace and name.
Parameters
name_space The desired class's name space. Typically, either the constant CLASS_BOX, for obex classes which
can instantiate inside of a Max patcher (e.g. boxes, UI objects, etc.), or the constant
CLASS_NOBOX, for classes which will only be used internally. Developers can define their own
name spaces as well, but this functionality is currently undocumented.
classname The name of the class to be looked up
Returns
If successful, this function returns the class's data pointer. Otherwise, it returns NULL.
Referenced by jit_class_findbyname().
38.2.4.6 class_findbyname_casefree()
t_class∗ class_findbyname_casefree (
t_symbol ∗ name_space,
t_symbol ∗ classname )
Finds the class pointer for a class, given the class's namespace and name.
Parameters
name_space The desired class's name space. Typically, either the constant CLASS_BOX, for obex classes which
can instantiate inside of a Max patcher (e.g. boxes, UI objects, etc.), or the constant
CLASS_NOBOX, for classes which will only be used internally. Developers can define their own
name spaces as well, but this functionality is currently undocumented.
classname The name of the class to be looked up (case free)
Cycling ’74
38.2 Classes 253
Returns
If successful, this function returns the class's data pointer. Otherwise, it returns NULL.
38.2.4.7 class_free()
t_max_err class_free (
t_class ∗ c )
Parameters
c The class pointer
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
Referenced by jit_class_free().
38.2.4.8 class_is_ui()
long class_is_ui (
t_class ∗ c )
Parameters
c The class pointer.
Returns
Cycling ’74
254 Module Documentation
38.2.4.9 class_nameget()
t_symbol∗ class_nameget (
t_class ∗ c )
Parameters
c The class pointer
Returns
Referenced by jit_class_nameget().
38.2.4.10 class_new()
t_class∗ class_new (
C74_CONST char ∗ name,
C74_CONST method mnew,
C74_CONST method mfree,
long size,
C74_CONST method mmenu,
short type,
... )
Initializes a class by informing Max of its name, instance creation and free functions, size and argument types.
Developers wishing to use obex class features (attributes, etc.) must use class_new() instead of the traditional setup()
function.
Parameters
Cycling ’74
38.2 Classes 255
Returns
This function returns the class pointer for the new object class. This pointer is used by numerous other functions
and should be stored in a global or static variable.
38.2.4.11 class_obexoffset_get()
long class_obexoffset_get (
t_class ∗ c )
Retrieves the byte-offset of the obex member of the class's data structure.
Parameters
c The class pointer
Returns
This function returns the byte-offset of the obex member of the class's data structure.
38.2.4.12 class_obexoffset_set()
void class_obexoffset_set (
t_class ∗ c,
long offset )
Registers the byte-offset of the obex member of the class's data structure with the previously defined object class.
Use of this function is required for obex-class objects. It must be called from main().
Parameters
c The class pointer
offset The byte-offset to the obex member of the object's data structure. Conventionally, the macro calcoffset is
used to calculate the offset.
Referenced by ext_main().
Cycling ’74
256 Module Documentation
38.2.4.13 class_register()
t_max_err class_register (
t_symbol ∗ name_space,
t_class ∗ c )
Parameters
name_space The desired class's name space. Typically, either the constant CLASS_BOX, for obex classes which
can instantiate inside of a Max patcher (e.g. boxes, UI objects, etc.), or the constant
CLASS_NOBOX, for classes which will only be used internally. Developers can define their own
name spaces as well, but this functionality is currently undocumented.
c The class pointer
Returns
This function returns the error code MAX_ERR_NONE if successful, or one of the other error codes defined in
e_max_errorcodes if unsuccessful.
38.2.4.14 class_subclass()
t_max_err class_subclass (
t_class ∗ superclass,
t_class ∗ subclass )
First call class_new on the subclass, then pass in to class_subclass. If constructor or destructor are NULL will use the
superclass constructor.
Parameters
superclass The superclass pointer.
subclass The subclass pointer.
Returns
Cycling ’74
38.2 Classes 257
38.2.4.15 class_super_construct()
t_object∗ class_super_construct (
t_class ∗ c,
... )
Use this instead of object_alloc if you want to call the super class constructor, but allocating enough memory for sub-
class.
Parameters
Returns
38.2.4.16 ext_main()
ext_main() is the entry point for an extern to be loaded, which all externs must implement this shared/common prototype
ensures that it will be exported correctly on all platforms.
Parameters
See also
Version
References A_CANT, A_FLOAT, A_GIMME, A_LONG, addbang(), addfloat(), addint(), addmess(), attr_offset_array←-
_new(), attr_offset_new(), ATTR_SET_OPAQUE, ATTR_SET_OPAQUE_USER, calcoffset, class_addattr(), class_←-
addmethod(), CLASS_BOX, class_new(), class_obexoffset_set(), class_register(), method, object_obex_dumpout(),
object_obex_quickref(), and setup().
Cycling ’74
258 Module Documentation
addbang
addfloat
addint
addmess
attr_offset_array_new
attr_offset_new
class_addattr
ext_main
class_addmethod
class_new
class_obexoffset_set
class_register
object_obex_dumpout
object_obex_quickref
setup
Cycling ’74
38.3 Old-Style Classes 259
Functions
• BEGIN_USING_C_LINKAGE void setup (t_messlist ∗∗ident, method makefun, method freefun, t_getbytes_size
size, method menufun, short type,...)
Use the setup() function to initialize your class by informing Max of its size, the name of your functions that create and
destroy instances, and the types of arguments passed to the instance creation function.
• void addmess (method f, char ∗s, short type,...)
Use addmess() to bind a function to a message other than the standard ones covered by addbang(), addint(), etc.
• void addbang (method f)
Used to bind a function to the common triggering message bang.
• void addint (method f)
Use addint() to bind a function to the int message received in the leftmost inlet.
• void addfloat (method f)
Use addfloat() to bind a function to the float message received in the leftmost inlet.
• void addinx (method f, short n)
Use addinx() to bind a function to a int message that will be received in an inlet other than the leftmost one.
• void addftx (method f, short n)
Use addftx() to bind a function to a float message that will be received in an inlet other than the leftmost one.
• void ∗ newobject (void ∗maxclass)
Use newobject to allocate the space for an instance of your class and initialize its object header.
• void freeobject (void ∗op)
Release the memory used by a Max object.
• void ∗ newinstance (t_symbol ∗s, short argc, t_atom ∗argv)
Make a new instance of an existing Max class.
• void alias (char ∗name)
Use the alias function to allow users to refer to your object by a name other than that of your shared library.
• void class_setname (char ∗obname, char ∗filename)
Use class_setname() to associate you object's name with it's filename on disk.
• void ∗ typedmess (t_object ∗op, t_symbol ∗msg, short argc, t_atom ∗argp)
Send a typed message directly to a Max object.
• method getfn (t_object ∗op, t_symbol ∗msg)
Use getfn() to send an untyped message to a Max object with error checking.
• method egetfn (t_object ∗op, t_symbol ∗msg)
Use egetfn() to send an untyped message to a Max object that always works.
• method zgetfn (t_object ∗op, t_symbol ∗msg)
Use zgetfn() to send an untyped message to a Max object without error checking.
Cycling ’74
260 Module Documentation
38.3.2.1 addbang()
void addbang (
method f )
Parameters
38.3.2.2 addfloat()
void addfloat (
method f )
Use addfloat() to bind a function to the float message received in the leftmost inlet.
Parameters
Referenced by ext_main().
38.3.2.3 addftx()
void addftx (
method f,
short n )
Use addftx() to bind a function to a float message that will be received in an inlet other than the leftmost one.
Cycling ’74
38.3 Old-Style Classes 261
Parameters
Remarks
This correspondence between inlet locations and messages is not automatic, but it is strongly suggested that you
follow existing practice. You must set the correspondence up when creating an object of your class with proper
use of intin and floatin in your instance creation function New Instance Routine.
38.3.2.4 addint()
void addint (
method f )
Use addint() to bind a function to the int message received in the leftmost inlet.
Parameters
Referenced by ext_main().
38.3.2.5 addinx()
void addinx (
method f,
short n )
Use addinx() to bind a function to a int message that will be received in an inlet other than the leftmost one.
Parameters
Remarks
This correspondence between inlet locations and messages is not automatic, but it is strongly suggested that you
follow existing practice. You must set the correspondence up when creating an object of your class with proper
use of intin and floatin in your instance creation function New Instance Routine.
Cycling ’74
262 Module Documentation
38.3.2.6 addmess()
void addmess (
method f,
char ∗ s,
short type,
... )
Use addmess() to bind a function to a message other than the standard ones covered by addbang(), addint(), etc.
Parameters
See also
38.3.2.7 alias()
void alias (
char ∗ name )
Use the alias function to allow users to refer to your object by a name other than that of your shared library.
Parameters
name An alternative name for the user to use to make an object of your class.
38.3.2.8 class_setname()
void class_setname (
Cycling ’74
38.3 Old-Style Classes 263
char ∗ obname,
char ∗ filename )
Use class_setname() to associate you object's name with it's filename on disk.
Parameters
obname A character string with the name of your object class as it appears in Max.
filename A character string with the name of your external's file as it appears on disk.
38.3.2.9 egetfn()
method egetfn (
t_object ∗ op,
t_symbol ∗ msg )
Use egetfn() to send an untyped message to a Max object that always works.
Parameters
Returns
egetfn returns a pointer to the method bound to the message selector msg in the receiver's message list. If the
method can't be found, a pointer to a do-nothing function is returned.
38.3.2.10 freeobject()
void freeobject (
void ∗ op )
freeobject() calls an object's free function, if any, then disposes the memory used by the object itself. freeobject() should
be used on any instance of a standard Max object data structure, with the exception of Qelems and Atombufs. Clocks,
Binbufs, Proxies, Exprs, etc. should be freed with freeobject().
Parameters
Cycling ’74
264 Module Documentation
Remarks
This function can be replaced by the use of object_free(). Unlike freeobject(), object_free() checkes to make sure
the pointer is not NULL before trying to free it.
See also
newobject()
object_free()
38.3.2.11 getfn()
method getfn (
t_object ∗ op,
t_symbol ∗ msg )
Use getfn() to send an untyped message to a Max object with error checking.
Parameters
Returns
getfn returns a pointer to the method bound to the message selector msg in the receiver's message list. It returns
0 and prints an error message in Max Window if the method can't be found.
38.3.2.12 newinstance()
void∗ newinstance (
t_symbol ∗ s,
short argc,
t_atom ∗ argv )
Parameters
s className Symbol specifying the name of the class of the instance to be created.
argc Count of arguments in argv.
argv Array of t_atoms; arguments to the class's instance creation function.
Cycling ’74
38.3 Old-Style Classes 265
Returns
A pointer to the created object, or 0 if the class didn't exist or there was another type of error in creating the
instance.
Remarks
This function creates a new instance of the specified class. Using newinstance is equivalent to typing something
in a New Object box when using Max. The difference is that no object box is created in any Patcher window, and
you can send messages to the object directly without connecting any patch cords. The messages can either be
type- checked (using typedmess) or non-type-checked (using the members of the getfn family).
This function is useful for taking advantage of other already-defined objects that you would like to use 'privately' in your
object, such as tables. See the source code for the coll object for an example of using a privately defined class.
38.3.2.13 newobject()
void∗ newobject (
void ∗ maxclass )
Use newobject to allocate the space for an instance of your class and initialize its object header.
Parameters
maxclass The global class variable initialized in your main routine by the setup function.
Returns
Remarks
You call newobject() when creating an instance of your class in your creation function. newobject allocates the
proper amount of memory for an object of your class and installs a pointer to your class in the object, so that it can
respond with your class's methods if it receives a message.
Referenced by max_jit_obex_new().
38.3.2.14 setup()
Cycling ’74
266 Module Documentation
method menufun,
short type,
... )
Use the setup() function to initialize your class by informing Max of its size, the name of your functions that create and
destroy instances, and the types of arguments passed to the instance creation function.
Cycling ’74
38.3 Old-Style Classes 267
Parameters
ident A global variable in your code that points to the initialized class.
makefun Your instance creation function.
freefun Your instance free function (see Chapter 7).
size The size of your objects data structure in bytes. Usually you use the C sizeof operator here.
menufun No longer used. You should pass NULL for this parameter.
type The first of a list of arguments passed to makefun when an object is created.
... Any additional arguments passed to makefun when an object is created. Together with the type
parameter, this creates a standard Max type list as enumerated in e_max_atomtypes. The final
argument of the type list should be a 0.
See also
Referenced by ext_main().
38.3.2.15 typedmess()
void∗ typedmess (
t_object ∗ op,
t_symbol ∗ msg,
short argc,
t_atom ∗ argp )
Parameters
op Max object that will receive the message.
msg The message selector.
argc Count of message arguments in argv.
argp Array of t_atoms; the message arguments.
Returns
If the receiver object can respond to the message, typedmess() returns the result. Otherwise, an error message
will be seen in the Max window and 0 will be returned.
Cycling ’74
268 Module Documentation
Remarks
typedmess sends a message to a Max object (receiver) a message with arguments. Note that the message must
be a t_symbol, not a character string, so you must call gensym on a string before passing it to typedmess. Also,
note that untyped messages defined for classes with the argument list A_CANT cannot be sent using typedmess.
You must use getfn() etc. instead.
Example:
//If you want to send a bang message to the object bang_me...
void *bangResult;
bangResult = typedmess(bang_me,gensym("bang"),0,0L);
Referenced by max_jit_mop_bang().
38.3.2.16 zgetfn()
method zgetfn (
t_object ∗ op,
t_symbol ∗ msg )
Use zgetfn() to send an untyped message to a Max object without error checking.
Parameters
Returns
zgetfn returns a pointer to the method bound to the message selector msg in the receiver's message list. It returns
0 but doesn't print an error message in Max Window if the method can't be found.
Referenced by max_jit_attr_getdump().
Cycling ’74
38.4 Inlets and Outlets 269
Functions
• void ∗ inlet_new (void ∗x, C74_CONST char ∗s)
Use inlet_new() to create an inlet that can receive a specific message or any message.
• void ∗ intin (void ∗x, short n)
Use intin() to create an inlet typed to receive only integers.
• void ∗ floatin (void ∗x, short n)
Use floatin() to create an inlet typed to receive only floats.
• void ∗ outlet_new (void ∗x, C74_CONST char ∗s)
Use outlet_new() to create an outlet that can send a specific non-standard message, or any message.
• void ∗ bangout (void ∗x)
Use bangout() to create an outlet that will always send the bang message.
• void ∗ intout (void ∗x)
Use intout() to create an outlet that will always send the int message.
• void ∗ floatout (void ∗x)
Use floatout() to create an outlet that will always send the float message.
• void ∗ listout (void ∗x)
Use listout() to create an outlet that will always send the list message.
• void ∗ outlet_bang (t_outlet ∗x)
Use outlet_bang() to send a bang message out an outlet.
• void ∗ outlet_int (t_outlet ∗x, t_atom_long n)
Use outlet_int() to send an int message out an outlet.
• void ∗ outlet_float (t_outlet ∗x, double f)
Use outlet_float() to send a float message out an outlet.
• void ∗ outlet_list (t_outlet ∗x, t_symbol ∗s, short ac, t_atom ∗av)
Use outlet_list() to send a list message out an outlet.
• void ∗ outlet_anything (t_outlet ∗x, t_symbol ∗s, short ac, t_atom ∗av)
Use outlet_anything() to send any message out an outlet.
• void ∗ proxy_new (void ∗x, long id, long ∗stuffloc)
Use proxy_new to create a new Proxy object.
• long proxy_getinlet (t_object ∗master)
Use proxy_getinlet to get the inlet number in which a message was received.
38.4.2.1 bangout()
void∗ bangout (
void ∗ x )
Use bangout() to create an outlet that will always send the bang message.
Cycling ’74
270 Module Documentation
Parameters
x Your object.
Returns
Remarks
You can send a bang message out a general purpose outlet, but creating an outlet using bangout() allows Max to
type-check the connection a user might make and refuse to connect the outlet to any object that cannot receive a
bang message. bangout() returns the created outlet.
38.4.2.2 floatin()
void∗ floatin (
void ∗ x,
short n )
Parameters
x Your object.
n Location of the inlet from 1 to 9. 1 is immediately to the right of the leftmost inlet.
Returns
38.4.2.3 floatout()
void∗ floatout (
void ∗ x )
Use floatout() to create an outlet that will always send the float message.
Parameters
x Your object.
Cycling ’74
38.4 Inlets and Outlets 271
Returns
38.4.2.4 inlet_new()
void∗ inlet_new (
void ∗ x,
C74_CONST char ∗ s )
Use inlet_new() to create an inlet that can receive a specific message or any message.
Parameters
x Your object.
s Character string of the message, or NULL to receive any message.
Returns
Remarks
inlet_new() ceates a general purpose inlet. You can use it in circumstances where you would like special messages
to be received in inlets other than the leftmost one. To create an inlet that receives a particular message, pass the
message's character string. For example, to create an inlet that receives only bang messages, do the following
inlet_new (myObject,"bang");
To create an inlet that can receive any message, pass NULL for msg
inlet_new (myObject, NULL);
Proxies are an alternative method for general-purpose inlets that have a number of advantages. If you create
multiple inlets as shown above, there would be no way to figure out which inlet received a message. See the
discussion in Creating and Using Proxies.
38.4.2.5 intin()
void∗ intin (
void ∗ x,
short n )
Cycling ’74
272 Module Documentation
Parameters
x Your object.
n Location of the inlet from 1 to 9. 1 is immediately to the right of the leftmost inlet.
Returns
Remarks
intin creates integer inlets. It takes a pointer to your newly created object and an integer n, from 1 to 9. The number
specifies the message type you'll get, so you can distinguish one inlet from another. For example, an integer sent
in inlet 1 will be of message type in1 and a floating point number sent in inlet 4 will be of type ft4. You use addinx()
and addftx() to add methods to respond to these messages.
The order you create additional inlets is important. If you want the rightmost inlet to be the have the highest number in-
or ft- message (which is usually the case), you should create the highest number message inlet first.
38.4.2.6 intout()
void∗ intout (
void ∗ x )
Use intout() to create an outlet that will always send the int message.
Parameters
x Your object.
Returns
Remarks
You can send a bang message out a general purpose outlet, but creating an outlet using bangout() allows Max to
type-check the connection a user might make and refuse to connect the outlet to any object that cannot receive a
bang message. bangout() returns the created outlet.
38.4.2.7 listout()
void∗ listout (
void ∗ x )
Use listout() to create an outlet that will always send the list message.
Cycling ’74
38.4 Inlets and Outlets 273
Parameters
x Your object.
Returns
38.4.2.8 outlet_anything()
void∗ outlet_anything (
t_outlet ∗ x,
t_symbol ∗ s,
short ac,
t_atom ∗ av )
Parameters
Returns
Remarks
This function lets you send an arbitrary message out an outlet. Here are a couple of examples of its use.
First, here's a hard way to send the bang message (see outlet_bang() for an easier way):
outlet_anything(myOutlet, gensym("bang"), 0, NIL);
Remarks
And here's an even harder way to send a single integer (instead of using outlet_int()).
t_atom myNumber;
atom_setlong(&myNumber, 432);
outlet_anything(myOutlet, gensym("int"), 1, &myNumber);
Notice that outlet_anything() expects the message argument as a t_symbol∗, so you must use gensym() on a
character string.
Cycling ’74
274 Module Documentation
If you'll be sending the same message a lot, you might call gensym() on the message string at initialization time and
store the result in a global variable to save the (significant) overhead of calling gensym() every time you want to send a
message.
Also, do not send lists using outlet_anything() with list as the selector argument. Use the outlet_list() function instead.
38.4.2.9 outlet_bang()
void∗ outlet_bang (
t_outlet ∗ x )
Parameters
Returns
38.4.2.10 outlet_float()
void∗ outlet_float (
t_outlet ∗ x,
double f )
Parameters
Returns
Cycling ’74
38.4 Inlets and Outlets 275
38.4.2.11 outlet_int()
void∗ outlet_int (
t_outlet ∗ x,
t_atom_long n )
Parameters
Returns
38.4.2.12 outlet_list()
void∗ outlet_list (
t_outlet ∗ x,
t_symbol ∗ s,
short ac,
t_atom ∗ av )
Parameters
Returns
Remarks
outlet_list() sends the list specified by argv and argc out the specified outlet. The outlet must have been created
with listout or outlet_new in your object creation function (see above). You create the list as an array of Atoms, but
the first item in the list must be an integer or float.
Cycling ’74
276 Module Documentation
Remarks
It's not a good idea to pass large lists to outlet_list that are comprised of local (automatic) variables. If the list is
small, as in the above example, there's no problem. If your object will regularly send lists, it might make sense to
keep an array of t_atoms inside your object's data structure.
38.4.2.13 outlet_new()
void∗ outlet_new (
void ∗ x,
C74_CONST char ∗ s )
Use outlet_new() to create an outlet that can send a specific non-standard message, or any message.
Parameters
x Your object.
s A C-string specifying the message that will be sent out this outlet, or NULL to indicate the outlet will be used to
send various messages. The advantage of this kind of outlet's flexibility is balanced by the fact that Max must
perform a message-lookup in real-time for every message sent through it, rather than when a patch is being
constructed, as is true for other types of outlets. Patchers execute faster when outlets are typed, since the
message lookup can be done before the program executes.
Returns
38.4.2.14 proxy_getinlet()
long proxy_getinlet (
t_object ∗ master )
Use proxy_getinlet to get the inlet number in which a message was received.
Note that the owner argument should point to your external object's instance, not a proxy object.
Cycling ’74
38.4 Inlets and Outlets 277
Parameters
master Your object.
Returns
Referenced by max_jit_obex_inletnumber_get().
38.4.2.15 proxy_new()
void∗ proxy_new (
void ∗ x,
long id,
long ∗ stuffloc )
Parameters
x Your object.
id A non-zero number to be written into your object when a message is received in this particular Proxy.
Normally, id will be the inlet number analogous to in1, in2 etc.
stuffloc A pointer to a location where the id value will be written.
Returns
Remarks
This routine creates a new Proxy object (that includes an inlet). It allows you to identify messages based on an id
value stored in the location specified by stuffLoc. You should store the pointer returned by proxy_new() because
you'll need to free all Proxies in your object's free function using object_free().
After your method has finished, Proxy sets the stuffLoc location back to 0, since it never sees messages coming in an
object's leftmost inlet. You'll know you received a message in the leftmost inlet if the contents of stuffLoc is 0. As of
Max 4.3, stuffLoc is not always guaranteed to be a correct indicator of the inlet in which a message was received. Use
proxy_getinlet() to determine the inlet number.
Referenced by max_jit_obex_proxy_new().
Cycling ’74
278 Module Documentation
Max provides a number of ways of storing and manipulating data at a high level.
Linked List
Quick Map
String Object
Database
Symbol Object
Data Storage
Atom Array
Dictionary
Hash Table
Index Map
Modules
• Atom Array
Max's atomarray object is a container for an array of atoms with an interface for manipulating that array.
• Database
Max's database ( i.e.
• Dictionary
Cycling ’74
38.5 Data Storage 279
Typedefs
Enumerations
• enum e_max_datastore_flags {
OBJ_FLAG_OBJ , OBJ_FLAG_REF , OBJ_FLAG_DATA , OBJ_FLAG_MEMORY ,
OBJ_FLAG_SILENT , OBJ_FLAG_INHERITABLE , OBJ_FLAG_ITERATING , OBJ_FLAG_CLONE ,
OBJ_FLAG_DANGER , OBJ_FLAG_DEBUG }
Flags used in linklist and hashtab objects.
Max provides a number of ways of storing and manipulating data at a high level.
It is recommended to use Max's data storage mechanisms where possible, as Max's systems are designed for thread-
safety and integration with the rest of Max API.
Cycling ’74
280 Module Documentation
38.5.2.1 t_cmpfn
Methods that require a comparison function pointer to be passed in use this type. It should return true or false
depending on the outcome of the comparison of the two linklist items passed in as arguments.
See also
linklist_match()
hashtab_findfirst()
indexmap_sort()
38.5.3.1 e_max_datastore_flags
enum e_max_datastore_flags
Enumerator
Max's atomarray object is a container for an array of atoms with an interface for manipulating that array.
Cycling ’74
38.6 Atom Array 281
Data Structures
• struct t_atomarray
The atomarray object.
Enumerations
• enum t_atomarray_flags
The atomarray flags.
Functions
Cycling ’74
282 Module Documentation
Max's atomarray object is a container for an array of atoms with an interface for manipulating that array.
It can be useful for passing lists as a single atom, such as for the return value of an A_GIMMEBACK method. It also
used frequently in when working with Max's t_dictionary object.
See also
Dictionary
38.6.2.1 t_atomarray_flags
enum t_atomarray_flags
Currently the only flag is ATOMARRAY_FLAG_FREECHILDREN. If set via atomarray_flags() the atomarray will free any
contained A_OBJ atoms when the atomarray is freed.
38.6.3.1 atomarray_appendatom()
void atomarray_appendatom (
t_atomarray ∗ x,
t_atom ∗ a )
Cycling ’74
38.6 Atom Array 283
Parameters
x The atomarray instance.
a A pointer to the new atom to append to the end of the array.
See also
atomarray_appendatoms()
atomarray_setatoms()
38.6.3.2 atomarray_appendatoms()
void atomarray_appendatoms (
t_atomarray ∗ x,
long ac,
t_atom ∗ av )
Parameters
x The atomarray instance.
ac The number of new atoms to be appended to the array.
av A pointer to the first of the new atoms to append to the end of the array.
See also
atomarray_appendatom()
atomarray_setatoms()
38.6.3.3 atomarray_chuckindex()
void atomarray_chuckindex (
t_atomarray ∗ x,
long index )
Cycling ’74
284 Module Documentation
Parameters
x The atomarray instance.
index The zero-based index of the atom to remove from the array.
38.6.3.4 atomarray_clear()
void atomarray_clear (
t_atomarray ∗ x )
Frees all of the atoms and sets the size to zero. This function does not perform a 'deep' free, meaning that any A_OBJ
atoms will not have their object's freed. Only the references to those objects contained in the atomarray will be freed.
Parameters
x The atomarray instance.
Returns
38.6.3.5 atomarray_clone()
void∗ atomarray_clone (
t_atomarray ∗ x )
Create a new atomarray object which is a full clone of another atomarray object.
Parameters
x The atomarray instance which is to be copied.
Returns
See also
atomarray_new()
Cycling ’74
38.6 Atom Array 285
38.6.3.6 atomarray_copyatoms()
t_max_err atomarray_copyatoms (
t_atomarray ∗ x,
long ∗ ac,
t_atom ∗∗ av )
Parameters
x The atomarray instance.
ac The address of a long where the number of atoms will be set.
av The address of a t_atom pointer where the atoms will be allocated and copied.
Returns
Remarks
You are responsible for freeing memory allocated for the copy of the atoms returned.
long ac = 0;
t_atom *av = NULL;
atomarray_copyatoms(anAtomarray, &ac, &av);
if(ac && av){
// do something with ac and av here...
sysmem_freeptr(av);
}
See also
atomarray_getatoms()
38.6.3.7 atomarray_duplicate()
void∗ atomarray_duplicate (
t_atomarray ∗ x )
Parameters
x The atomarray instance which is to be copied.
Cycling ’74
286 Module Documentation
Returns
See also
atomarray_new()
38.6.3.8 atomarray_flags()
void atomarray_flags (
t_atomarray ∗ x,
long flags )
Parameters
x The atomarray instance.
flags The new value for the flags.
38.6.3.9 atomarray_funall()
void atomarray_funall (
t_atomarray ∗ x,
method fun,
void ∗ arg )
Call the specified function for every item in the atom array.
Parameters
x The atomarray instance.
fun The function to call, specified as function pointer cast to a Max method.
arg An argument that you would like to pass to the function being called.
Returns
Cycling ’74
38.6 Atom Array 287
Remarks
The atomarray_funall() method will call your function for every item in the list. It will pass both a pointer to the item
in the list, and any argument that you provide. The following example shows a function that could be called by
hashtab_funall().
void myFun(t_atom *a, void *myArg)
{
// do something with a and myArg here
// a is the atom in the atom array
}
See also
linklist_funall()
hashtab_funall()
38.6.3.10 atomarray_getatoms()
t_max_err atomarray_getatoms (
t_atomarray ∗ x,
long ∗ ac,
t_atom ∗∗ av )
This method does not copy the atoms, btu simply provides access to them. To retrieve a copy of the atoms use
atomarray_copyatoms().
Parameters
x The atomarray instance.
ac The address of a long where the number of atoms will be set.
av The address of a t_atom pointer where the address of the first atom of the array will be set.
Returns
See also
atomarray_copyatoms()
38.6.3.11 atomarray_getflags()
long atomarray_getflags (
t_atomarray ∗ x )
Cycling ’74
288 Module Documentation
Parameters
x The atomarray instance.
Returns
38.6.3.12 atomarray_getindex()
t_max_err atomarray_getindex (
t_atomarray ∗ x,
long index,
t_atom ∗ av )
Parameters
x The atomarray instance.
index The zero-based index into the array from which to retrieve an atom pointer.
av The address of an atom to contain the copy.
Returns
Remarks
Example:
{
t_atom a;
// fetch a copy of the second atom in a previously existing array
atomarray_getindex(anAtomarray, 1, &a);
// do something with the atom here...
}
38.6.3.13 atomarray_getsize()
t_atom_long atomarray_getsize (
t_atomarray ∗ x )
Cycling ’74
38.6 Atom Array 289
Parameters
x The atomarray instance.
Returns
38.6.3.14 atomarray_new()
Note that atoms provided to this function will be copied. The copies stored internally to the atomarray instance. You can
free the atomarray by calling object_free().
Parameters
Returns
Remarks
Note that due to the unusual prototype of this method that you cannot instantiate this object using the
object_new_typed() function. If you wish to use the dynamically bound creator to instantiate the object, you should
instead should use object_new() as demonstrated below. The primary reason that you might choose to instantiate
an atomarray using object_new() instead of atomarray_new() is for using the atomarray object in code that is also
intended to run in Max 4.
object_new(CLASS_NOBOX, gensym("atomarray"), argc, argv);
See also
atomarray_duplicate()
Cycling ’74
290 Module Documentation
38.6.3.15 atomarray_setatoms()
t_max_err atomarray_setatoms (
t_atomarray ∗ x,
long ac,
t_atom ∗ av )
Replace the existing array contents with a new set of atoms Note that atoms provided to this function will be copied.
Parameters
x The atomarray instance.
ac The number of atoms to be initially contained in the atomarray.
av A pointer to the first of an array of atoms to initially copy into the atomarray.
Returns
38.7 Database
Typedefs
Cycling ’74
38.7 Database 291
Functions
• BEGIN_USING_C_LINKAGE t_max_err db_open (t_symbol ∗dbname, const char ∗fullpath, t_database ∗∗db)
Create an instance of a database.
• t_max_err db_open_ext (t_symbol ∗dbname, const char ∗fullpath, t_database ∗∗db, long flags)
Create an instance of a database.
• t_max_err db_close (t_database ∗∗db)
Close an open database.
• t_max_err db_query (t_database ∗db, t_db_result ∗∗dbresult, const char ∗sql,...)
Execute a SQL query on the database.
• t_max_err db_vquery (t_database ∗db, t_db_result ∗∗dbresult, const char ∗s, va_list ap)
Execute a SQL query on the database.
• t_max_err db_query_direct (t_database ∗db, t_db_result ∗∗dbresult, const char ∗sql)
Execute a SQL query on the database.
• t_max_err db_query_silent (t_database ∗db, t_db_result ∗∗dbresult, const char ∗sql,...)
Execute a SQL query on the database, temporarily overriding the database's error logging attribute.
• t_max_err db_query_getlastinsertid (t_database ∗db, long ∗id)
Determine the id (key) number for the most recent INSERT query executed on the database.
• t_max_err db_query_table_new (t_database ∗db, const char ∗tablename)
Create a new table in a database.
• t_max_err db_query_table_addcolumn (t_database ∗db, const char ∗tablename, const char ∗columnname, const
char ∗columntype, const char ∗flags)
Add a new column to an existing table in a database.
• t_max_err db_transaction_start (t_database ∗db)
Begin a database transaction.
• t_max_err db_transaction_end (t_database ∗db)
Finalize a database transaction.
• t_max_err db_transaction_flush (t_database ∗db)
Force any open transactions to close.
• t_max_err db_view_create (t_database ∗db, const char ∗sql, t_db_view ∗∗dbview)
A database view is a way of looking at a particular set of records in the database.
• t_max_err db_view_remove (t_database ∗db, t_db_view ∗∗dbview)
Remove a database view created using db_view_create().
• t_max_err db_view_getresult (t_db_view ∗dbview, t_db_result ∗∗result)
Fetch the pointer for a t_db_view's query result.
• t_max_err db_view_setquery (t_db_view ∗dbview, char ∗newquery)
Set the query used by the view.
• char ∗∗ db_result_nextrecord (t_db_result ∗result)
Return the next record from a set of results that you are walking.
• void db_result_reset (t_db_result ∗result)
Reset the interface for walking a result's record list to the first record.
• void db_result_clear (t_db_result ∗result)
Zero-out a database result.
• long db_result_numrecords (t_db_result ∗result)
Return a count of all records in the query result.
• long db_result_numfields (t_db_result ∗result)
Return a count of all fields (columns) in the query result.
Cycling ’74
292 Module Documentation
t_database ) support currently consists of a SQLite ( http://sqlite.org ) extension which is loaded dynamically
by Max at launch time. Because it is loaded dynamically, all interfacing with the sqlite object relies on Max's message
passing interface, using object_method() and related functions.
For most common database needs, a C-interface is defined in the ext_database.h header file and implemented in
the ext_database.c source file. The functions defined in this interface wrap the message passing calls and provide a
convenient means by which you can work with databases. ext_database.c is located in the 'common' folder inside of
the 'max-includes' folder. If you use any of the functions defined ext_database.h, you will need to add ext_database.c to
your project.
38.7.2.1 t_database
A database object.
38.7.2.2 t_db_result
Cycling ’74
38.7 Database 293
38.7.2.3 t_db_view
A database view wraps a query and a result for a given database, and is always updated and in-sync with the database.
38.7.3.1 db_close()
t_max_err db_close (
t_database ∗∗ db )
Parameters
db The address of the t_database pointer for your database instance. The pointer will be freed and set NULL
upon return.
Returns
An error code.
38.7.3.2 db_open()
Parameters
Returns
An error code.
38.7.3.3 db_open_ext()
t_max_err db_open_ext (
t_symbol ∗ dbname,
const char ∗ fullpath,
t_database ∗∗ db,
long flags )
Parameters
Returns
An error code.
38.7.3.4 db_query()
t_max_err db_query (
t_database ∗ db,
t_db_result ∗∗ dbresult,
const char ∗ sql,
... )
Parameters
... If an sprintf() formatting codes are used in the sql string, these values will be interpolated into the sql
string.
38.7 Database 295
Returns
An error code.
38.7.3.5 db_query_direct()
t_max_err db_query_direct (
t_database ∗ db,
t_db_result ∗∗ dbresult,
const char ∗ sql )
Parameters
Returns
An error code.
38.7.3.6 db_query_getlastinsertid()
t_max_err db_query_getlastinsertid (
t_database ∗ db,
long ∗ id )
Determine the id (key) number for the most recent INSERT query executed on the database.
Parameters
Returns
An error code.
Cycling ’74
296 Module Documentation
38.7.3.7 db_query_silent()
t_max_err db_query_silent (
t_database ∗ db,
t_db_result ∗∗ dbresult,
const char ∗ sql,
... )
Execute a SQL query on the database, temporarily overriding the database's error logging attribute.
Parameters
Returns
An error code.
38.7.3.8 db_query_table_addcolumn()
t_max_err db_query_table_addcolumn (
t_database ∗ db,
const char ∗ tablename,
const char ∗ columnname,
const char ∗ columntype,
const char ∗ flags )
Parameters
Cycling ’74
38.7 Database 297
Returns
An error code.
38.7.3.9 db_query_table_new()
t_max_err db_query_table_new (
t_database ∗ db,
const char ∗ tablename )
Parameters
Returns
An error code.
38.7.3.10 db_result_clear()
void db_result_clear (
t_db_result ∗ result )
Parameters
38.7.3.11 db_result_datetimeinseconds()
t_ptr_uint db_result_datetimeinseconds (
t_db_result ∗ result,
long recordindex,
long fieldindex )
Cycling ’74
298 Module Documentation
Return a single value from a result according to its index and field coordinates.
The value will be coerced from an expected datetime field into seconds.
Parameters
Returns
38.7.3.12 db_result_fieldname()
char∗ db_result_fieldname (
t_db_result ∗ result,
long fieldindex )
Parameters
Returns
38.7.3.13 db_result_float()
float db_result_float (
t_db_result ∗ result,
long recordindex,
long fieldindex )
Return a single value from a result according to its index and field coordinates.
Cycling ’74
38.7 Database 299
Parameters
Returns
The content of the specified cell from the result scanned out to a float.
38.7.3.14 db_result_long()
long db_result_long (
t_db_result ∗ result,
long recordindex,
long fieldindex )
Return a single value from a result according to its index and field coordinates.
Parameters
Returns
The content of the specified cell from the result scanned out to a long int.
38.7.3.15 db_result_nextrecord()
char∗∗ db_result_nextrecord (
t_db_result ∗ result )
Return the next record from a set of results that you are walking.
When you are returned a result from a query of the database, the result is prepared for walking the results from the
beginning. You can also reset the result manually to the beginning of the record list by calling db_result_reset().
Cycling ’74
300 Module Documentation
Parameters
Returns
An array of C-Strings with the values for every requested column (field) of a database record. To find out how
many columns are represented in the array, use db_result_numfields().
38.7.3.16 db_result_numfields()
long db_result_numfields (
t_db_result ∗ result )
Parameters
Returns
38.7.3.17 db_result_numrecords()
long db_result_numrecords (
t_db_result ∗ result )
Parameters
Returns
Cycling ’74
38.7 Database 301
38.7.3.18 db_result_reset()
void db_result_reset (
t_db_result ∗ result )
Reset the interface for walking a result's record list to the first record.
Parameters
38.7.3.19 db_result_string()
char∗ db_result_string (
t_db_result ∗ result,
long recordindex,
long fieldindex )
Return a single value from a result according to its index and field coordinates.
Parameters
Returns
38.7.3.20 db_transaction_end()
t_max_err db_transaction_end (
t_database ∗ db )
Parameters
Cycling ’74
302 Module Documentation
Returns
An error code.
38.7.3.21 db_transaction_flush()
t_max_err db_transaction_flush (
t_database ∗ db )
Parameters
Returns
An error code.
38.7.3.22 db_transaction_start()
t_max_err db_transaction_start (
t_database ∗ db )
When you are working with a file-based database, then the database will not be flushed to disk until db_transacation←-
_end() is called. This means that you can _much_ more efficiently execute a sequence of queries in one transaction
rather than independently.
That database object reference counts transactions, so it is possible nest calls to db_transacation_start() and db_←-
transacation_end(). It is important to balance all calls with db_transacation_end() or the database contents will never
be flushed to disk.
Parameters
Returns
An error code.
Cycling ’74
38.7 Database 303
38.7.3.23 db_util_datetostring()
void db_util_datetostring (
const t_ptr_uint date,
char ∗ string )
Parameters
date The datetime represented in seconds.
string The address of a valid C-string whose contents will be set to a SQL-ready string format upon return.
38.7.3.24 db_util_stringtodate()
void db_util_stringtodate (
const char ∗ string,
t_ptr_uint ∗ date )
Parameters
38.7.3.25 db_view_create()
t_max_err db_view_create (
t_database ∗ db,
const char ∗ sql,
t_db_view ∗∗ dbview )
This particular set of records is defined with a standard SQL query, and the view maintains a copy of the results of the
query internally. Any time the database is modified the internal result set is updated, and any objects listening to the
view are notified via object_notify().
Parameters
Returns
An error code.
38.7.3.26 db_view_getresult()
t_max_err db_view_getresult (
t_db_view ∗ dbview,
t_db_result ∗∗ result )
Parameters
Returns
An error code.
38.7.3.27 db_view_remove()
t_max_err db_view_remove (
t_database ∗ db,
t_db_view ∗∗ dbview )
Parameters
db The t_database pointer for your database instance for which this view was created.
dbview The address of the t_db_view pointer for the view. This pointer will be freed and set NULL upon return.
Returns
An error code.
Cycling ’74
38.8 Dictionary 305
38.7.3.28 db_view_setquery()
t_max_err db_view_setquery (
t_db_view ∗ dbview,
char ∗ newquery )
Parameters
Returns
An error code.
38.7.3.29 db_vquery()
t_max_err db_vquery (
t_database ∗ db,
t_db_result ∗∗ dbresult,
const char ∗ s,
va_list ap )
Parameters
Returns
An error code.
38.8 Dictionary
Cycling ’74
306 Module Documentation
Data Structures
• struct t_dictionary_entry
A dictionary entry.
• struct t_dictionary
The dictionary object.
Functions
Cycling ’74
38.8 Dictionary 307
Cycling ’74
308 Module Documentation