This article discusses the use of a package-template created to provide a basic directory structure. In this article, we will use the word template to denote a "model" for a package - we will not be talking about the usual jMax templates that are, in fact, patches.
The template used to create new external packages is available on the jMax CVS repository. The name of the module is jmaxtemplate. This template contains :
The configure.in scripts
The Makefile.am scripts
The autogen.sh script
The source code of an empty control object
The source code of an empty DSP object
The template proposes a directory structure for a jMax package. When creating a new package, you should follow this structure as jMax will expect some of the directories to exist when it loads external packages. The structure has the following outline :
name_of_package
Should you distribute several packages in one archive, we recommend the following structure :
top_directory
The entry point of your package will be the function name_of_package_config which must conform to the following prototype (see the article about porting existing packages for further discussion on this) :
void name_of_package_config(void)
So if your package answers to the name foo, you must define the following function in your library :
void foo_config(void)
The entry point in Java is a class named Name_of_package. Beware! The first letter must be upper-case, as in all Java class names. If you named your package foozy_stuff, you’ll have to name your class Foozy_stuff [1].
This class must implement the JMaxPackage interface, as in the following example :
import ircam.jmax.JMaxPackage;
// ...
// ...
public class Foo implements JMaxPackage
{
// ...
}
</pre>
The package configuration file
Some packages require a configuration file. You package needs one if (pick one) :
To create such a configuration file, use the package editor provided with jMax :
First, create a new package :
The "Poject" menu Creating a new package configuration file. |
In the New Package dialog box, type the name of the package and the directory in which to save the configuration file. If you indicate the location directory, the configuration file will be written to : location/package_name/package_name.jpkg.
The "New Project" dialog box Choosing a name and a location for a project configuration file. |
Dependencies on other packages should be declared in the packages tab.
The following picture shows a package foo depending on packages data and midi.
The "Package Editor" dialog box Adding dependencies to your package. |
Upon installing jMax, the jmax.m4 script is installed as well. This file contains the definition for the m4 macro AM_JMAX_CONFIG which sets the following variables :
JMAX_INCLUDES : compiler option to get the correct include paths
JMAX_LDFLAGS : linker option to locate the shared library to link with
JMAX_PACKAGES_DIR : the standard path for packages
It is highly recommended to use the macro and the variables describe above inside your configure.in/Makefile.am scripts.
Should jMax not have been installed in the standard directories, there is a chance that aclocal fails to find the definition of the AM_JMAX_CONFIG macro. The solution to this is to add the installation directory of the jmax.m4 file in aclocal’s search path, using the -I command-line option.
When using the package template, there are several things you have to change to adapt it to your package.
In the root configure.in file (i.e. the one in the root directory of your package), you must set :
The packages/Makefile.am script : update the value of the SUBDIRS variable by indicating the names of all subdirectories of the current directory. If you have the packages foo1 and foo2, your SUBDIRS variables will look like this : SUBDIRS = foo1 foo2.
The packages/name_of_first_package/configure.in : change :
This might look somewhat strange, but there are packages that are not only intended for jMax, but that fit into other programs as well. Imagine a package foo1 that has a wrapper to include it in jMax [2] ; you might want to distribute a jmax_foo1 package. You’ll then have: PACKAGE_NAME=jmax_foo1 JMAX_PACKAGE_NAME=foo1
Of course, the C entry point will be foo1_config() and the Java entry point will be a class named Foo1.
[1] And of course, your C code will contain a foozy_stuff_config() function
[2] It is recommended to add the jmax_ prefix (or _jmax suffix) to the generated archives, so as to avoid confusion when using common names like fft, VST, SDIF, ...
In jMax 2.5 (which we will from now on simply refer to as "2.5"), the first thing that was looked for was an fts_module_t structure exported by your package. If we consider the case of a package named ’foo’, you had to export that structure in ’libfoo.so’. This structure used to tell 2.5 where to find the initialization and the shutdown functions, and gave it some additional information for documentation purposes.
This structure no longer exists in 4.0. Instead, jMax will automatically look for a function named void foo_config(void). The shutdown function is now gone and so are those two documentation strings mentioned above. Please note that this foo_config() function must not be declared static, otherwise it won’t be exported from your shared library and jMax will complain and so will your users and you will never see your package used by anyone ever again. So don’t make it static.
What if you have resources that need to be freed when your package is no longer required? Since the shutdown function is gone, here’s what you can do :
All your objects will be destroyed when the patcher is closed, so you need not worry about resources held by individual instances of your objects
If you have shared resources, you can have a singleton object on the patcher that will manage these.
You can use atexit() to register your shutdown function, but it will only be called when FTS is shut down.
The way you define objects, methods inlets and outlets have changed. In 2.5, you would declare a fixed number of inlets and outlets for the class, and if you wanted a variable number of these, you had to resort to using meta-classes. In 4.0, you still declare a number of inlets/outlets, but each instance of this class (i.e. each object) is free to have more if needed (call fts_object_set_inlets/outlets_number()). If extra inlets/outlets are added, they will default to the same type as the last inlet (i.e. the last inlet declared for the class). Of course, meta-classes are therefore obsolete and they have now retired.
Methods used to be defined with the fts_method_define*() set of functions. These would associate an inlet with a certain type of data to be received and a method to call upon receiving that kind of data on that inlet. 4.0 introduced a slight change in this : the fts_class_message*(class, symbol, type, method, ...) set of functions now declare a method that handles messages identified by a given symbol. There is no special inlet associated with a message : every inlet will work. If you expect something else than a message, use fts_class_inlet(class, winlet, type, method) and friends [1] to associate a given inlet with a given type and a given function.
Constructors and destructors are no longer defined as methods associated with fts_SystemInlet and fts_s_init/destroy. Instead, they are given directly as arguments to the fts_class_init(class, size, constructor, destructor) function.
The object_instantiate function has changed signature, and nowadays it only takes a fts_class_t* argument and returns nothing.
Version 4.0 no longer uses alarms. Since these were of quite common use, we’ll see here what happened to them. But this page is not exhaustive, and other features that are gone or that have been added will not be discussed here (see the paragraph about autoport for more details).
As stated, alarms are history. FTS now uses timebases - some sort of strange animals that holds a list of functions to call and when to call them, and that calls them when the time is right. FTS provides a default timebase for free, that you can get with the fts_get_timebase() function. Register your functions with fts_timebase_add_call().
Some other minor changes in the API will contribute to the already long list of compile-errors that you’ll get at this point. Not to worry, it’s often just simple names that have changed :
fts_set/get_ptr() have become fts_set/get_pointer(). Same goes for the fts_word_*() variants.
fts_new_symbol_copy() doesn’t exist any more : use fts_new_symbol()
The ?vec_atom_get() family of functions are now replaced by a single cast : (?vec_t*)fts_get_object()
fts_symbol_name is gone
fts_bloc*() memory operations are obsolete. Use the standard fts_malloc() and friends instead.
Now there is no doubt that porting a package in such circumstances can be a tedious task. We could have kept most of the above as it was, but the truth is that 4.0 works much better than 2.5, and a few minor changes and face-lifts are the price to pay.
Since we didn’t want to leave all of you package developers in the dirt, we have created a small utility script that will help you in porting your package. Meet autoport. He lives in the utils/update subdirectory of the jMax source code tree. He’s not good at playing chess, but he’ll do some cool things for you : change your source code (you heard the man : make a backup first!) where he can and put comments to tell you what he did, remove obsolete code, and what he cannot change (like alarms) he will warn you about.
That tool is far from perfect, but we hope it will at least get you started on your code-tweaking. You need Perl to make it run, but if you are using Linux, you probably already have it. If not, get it : it’s free.
If you knew and understood the former API, you will have no problem with the new one, except that you’ll have to get to know it. But porting a package should be fairly simple if you stuck to the standard features of FTS.
Once you’ve done all this, you’re not done yet : you have to package it. But that’s described in another article.
We hope to see many packages be deveoped or ported to 4.0. If you have trouble, you can always post a message to jmax@listes.ircam.fr.
[1] There are shorthand macros defined for each predefined type of data, like fts_class_inlet_int().