Writing your own Max or Jitter externals in C or C++ isn’t terribly hard, once you’ve wrapped your head around the API’s C approach to object oriented programming. However, it does involve a fair bit of boilerplate. This is especially true for adding attributes to an object – and triply so if this attribute has custom getter and setter methods.
The cv.jit collection now contains more than a few externals and I find myself spending more time trying to find ways to automate some of the repetitive tasks that are required for keeping it up to date. One of the tools I just made is a nifty Ruby script for automatically generating all the necessary attribute-related boilerplate. Simply invoke it at the command line with only a few arguments and it generates a .c file containing the necessary code. It parses the arguments “-c”, “-l”, “-f”, “-d”, “-s” and “-a” as “char”, “long”, “float32”, “float64”, “symbol” and “atom” types. Numbers (if there are any) as the number of elements in a list. The arguments “-get” and “-set” specify that the attribute has a custom getter and setter, while “-clip” will add a filter to clip argument values. Any other argument is going to be parsed as the name of the attribute, unless it begins with a “-“, in which case, it’s interpreted as your external’s name (periods are automatically converted to underscores.)
For example:
ruby ./jitargs.rb -f -cv.jit.bigbrother foo
This generates a file “jitter_args.c” in the current directory that looks like this:
//setter/getter declarations
//attribute variables
float foo;
//setters/getters
//attribute registration
attr = (t_jit_object *)jit_object_new(_jit_sym_jit_attr_offset,"foo",_jit_sym_float32,
attrflags,(method)0L,(method)0L,calcoffset(t_cv_jit_bigbrother,foo));
jit_attr_addfilterset_clip(attr,0,1,TRUE,TRUE);
jit_class_addattr(_cv_jit_bigbrother_class,attr);
//attribute initialization
x->foo = 0;
If you wish to add more attributes, just run the script again with different arguments, new code will be inserted in the appropriate place. For example, by running the following:
ruby ./jitargs.rb -a -get -set 2 -cv.jit.bigbrother bar
The file above is modified to:
//setter/getter declarations
t_jit_err cv_jit_bigbrother_set_bar(t_cv_jit_bigbrother *x, void *attr, long ac, t_atom *av);
t_jit_err cv_jit_bigbrother_get_bar(t_cv_jit_bigbrother *x, void *attr, long *ac, t_atom **av);
//attribute variables
long barcount;
t_atom bar[2];
float foo;
//setters/getters
t_jit_err cv_jit_bigbrother_set_bar(t_cv_jit_bigbrother *x, void *attr, long ac, t_atom *av){
if(ac < 2){
//Not enough parameters?
return JIT_ERR_NONE;
}
return JIT_ERR_NONE;
}
t_jit_err cv_jit_bigbrother_get_bar(t_cv_jit_bigbrother *x, void *attr, long *ac, t_atom **av){
int i;
if ((*ac)&&(*av)) {
//memory passed in, use it
} else {
//otherwise allocate memory
*ac = 2;
if (!(*av = jit_getbytes(sizeof(t_atom)*(*ac)))) {
*ac = 0;
return JIT_ERR_OUT_OF_MEM;
}
}
for(i=0;i<2;i++)av[i] = x->bar[i];
return JIT_ERR_NONE;
}
//attribute registration
attr = (t_jit_object *)jit_object_new(_jit_sym_jit_attr_offset_array, "bar", _jit_sym_atom, 2,
attrflags, (method)cv_jit_bigbrother_get_bar,(method)cv_jit_bigbrother_set_bar,
calcoffset(t_cv_jit_bigbrother, barcount),calcoffset(t_cv_jit_bigbrother,bar));
jit_class_addattr(_cv_jit_bigbrother_class,attr);
attr = (t_jit_object *)jit_object_new(_jit_sym_jit_attr_offset,"foo",_jit_sym_float32,
attrflags,(method)0L,(method)0L,calcoffset(t_cv_jit_bigbrother,foo));
jit_attr_addfilterset_clip(attr,0,1,TRUE,TRUE);
jit_class_addattr(_cv_jit_bigbrother_class,attr);
//attribute initialization
jit_atom_setlong(&x->bar[0],0);
jit_atom_setlong(&x->bar[1],0);
x->foo = 0;
All you need to do now is copy and paste the code at the appropriate places. Of course, if I was really crazy, I would write a script that parses and modifies the actual external source but I’ll leave that as an exercise for the reader.
Leave a Reply