< home

MAX/MSP Externals Tutorial

Any object with an OpenExposition interface (i.e., descendants of class Exposer) can very easily be built as a MAX/MSP external, and then used in MAX/MSP patches.

The class Test discussed in the OpenExposition Introduction can become a loadable MAX/MSP external like this:

#include <expo/ExpositionSystemMAX.h>
#include "Test.h"

using namespace expo;

int main()
{
    ExpositionSystemMAX *ES =
        new ExpositionSystemMAX(new TemplateExposerFactory<TestExposer>());

    ES->Run();
    return 0;
}

The main part is specifying the class to be encapsulated into the MAX external (i.e., TestExposer). Embedding the OpenExposition Python interpreter into MAX is done nearly identically, through the PythonExposer class instead of Test:

#include <expo/ExpositionSystemMAX.h>
#include <expo/PythonExposition.h>

using namespace expo;

int main()
{
    ExpositionSystemMAX *ES =
        new ExpositionSystemMAX(new TemplateExposerFactory<PythonExposer>());

    ES->Run();
    return 0;
}

Building this into a bundle on OS X, or a dynamic link library on Windows, will yield a MAX/MSP external. The OpenExposition distribution has example projects (TestMAX and MAXPython) that build such objects. MAXPython is an embedded Python interpreter for MAX/MSP, and is a very convenient (and automatic) consequence of OpenExposition support for both Python and MAX.

In general, the advantage of using OpenExposition is that it makes compiling MAX/MSP externals extremely easy. Also, having an OpenExposition interface, the same object can be tested or used outside of the MAX environment as well (for example, the TestMAX object is also accessible via a native OS GUI as well as through Python and speech in the Test example).

Interfacing with the MAX/MSP object

OpenExposition tries to construct the inlets and outlets to the object intelligently, using the exposer's specified expositions and properties. Take for example the exposer for the Test class:

class TestExposer : public Test, public Exposer
{
public:
    // Whenever an Exposer constructor is called, the instance is pushed on a stack
    // as the current exposer instance.  All instantiated expositions are automatically
    // assigned to the current exposer.
    TestExposer(string const &name="Test") : Exposer(name)
    {
        // this provides access to a method, which we mark as the "final" action of the exposer
        *new MethodExpo<double,Test>(*this, &Test::sum, "sum")
            << new Property(Property::final);
        // this provides direct access to the flag variable
        *new VariableExpo<bool>(flag, "flag")
            << new InfluencesProperty(GetExpo("sum"));
        // this provides direct access to the a variable
        *new VariableExpo<int>(a, "a")
            << new InfluencesProperty(GetExpo("sum"));
        // this provides access to a variable through a set of get/set methods
        *new PropertyExpo<double, Test>(*this, &Test::GetB, &Test::SetB, "b")
            << new InfluencesProperty(GetExpo("sum"));
        
        // the following call will mark the end of construction for this exposer
        DoneConstructing();
    }
}; // end class TestExposer

It marks the sum method with the final property, which will make banging the object in MAX execute that method. Also, any exposed variable that influences sum (specified via InfluencesProperty) will get its own inlet. The exception here are string variables - only one string variable can be accessed via the inlets, and if such a string variable exists sending a string to ANY outlet will set it.

Such an example can be seen in OpenExposition's Python exposer:

PythonExposer::PythonExposer (string const &name) : Exposer(name), accumulate(false)
{
...
    // Initialize exposer
    *new VariableExpo<string> (output, "output")
        << new Property(Property::multi_line)
        << new Property(Property::read_only);
    *new MethodExpo<void, PythonExposer> (*this, &PythonExposer::RunCode, "Run Code")
        << new InfluencesProperty(GetExpo("output"))
        << new Property(Property::final);
    *new VariableExpo<string> (buffer, "code")
        << new Property(Property::multi_line)
        << new InfluencesProperty(GetExpo("Run Code"));
    *new VariableExpo<bool> (accumulate, "Accumulate Output")
        << new InfluencesProperty(GetExpo("Run Code"), true);
    DoneConstructing();

The code variable can be set by sending a single string (i.e. message) to any of the inlets of the MAXPython external. Also, because it influences the Run Code method, which is final, this will trigger its execution.

Any OpenExposition MAX external also supports get, set, and call messages (e.g., set a 1, get b, or call "Run Code". get and call will output the result on the leftmost outlet. Additional outlets will be created for any variables that are denoted as read-only, and influenced by the exposition marked final. Variable output in the Python example is such a variable.

The OpenExposition downloads for OS X and Windows have precompiled TestMAX and MAXPython externals, as well as patches that demonstrate their use.

Building your own MAX/MSP externals

To build your own external, I suggest duplicating the target/project for the TestMAX example in the OpenExposition distribution. Make sure you read the Installation Information for instructions on downloading and set-up for building and using the OpenExposition library.

Then, in your copy of TestMAX.cpp (or whatever you rename it to) target/project, instead of "Test.h" include your own file that defines an OpenExposition interface through a class that is derived from class Exposer, and substitute the name of your class for the one occurence of TestExposer inside TestMAX.cpp. Use the properties as specified in Interfacing with the MAX/MSP object to guide the inlet/outlet behavior. Currently, your Exposer class MUST have a default constructor (see the examples above) in order for this to work.

 

Documentation generated on 14 Jun 2006 for OpenExposition by  doxygen 1.4.6>
Development hosted by SourceForge.net Logo