Porting MXJ Buf.OP to a C object realistic undertaking?

anonymous's icon

? Buffer.Op duplicates existing max control of the buffer~ object? if you pass the correct args to the buffer~ object, it is the same as doing so in Java with buffer.op?

Timothy Place's icon

This isn't the cleanest code, but in the event that it helps, here is the source code for the tap.buffer.peak~ object in Tap.Tools.

Cheers,
Tim

/*
 * tap.buffer.peak~
 * an msp object for finding the hottest sample of a buffer
 * by Timothy Place
 * Copyright © 2004 Electrotap L.L.C.
 */

#include "TapToolsObject.h"
#include "ext_globalsymbol.h"
#include "buffer.h"
#include "ext_atomic.h"

typedef struct _peak {
    t_object     obj;
    void*        outlet;
    t_symbol*    sym;        // Current Buffer Name.
    t_buffer*    buf;        // Buffer Reference.
    long        index;        // The cached index of the peak sample.
    long        changed;    // Flag to indicate if the buffer changed since the last time.
} t_peak;

// Function & Method Prototypes
void*        peak_new(t_symbol *s);
void        peak_free(t_peak *x);
t_max_err    peak_notify(t_peak *x, t_symbol *s, t_symbol *msg, void *sender, void *data);
void        peak_assist(t_peak *x, void *b, long m, long a, char *s);
void        peak_dsp(t_peak *x, t_signal **sp, short *count);
void        peak_set(t_peak *x, t_symbol *s);
void        peak_calc(t_peak *x);

static t_class*        s_peak_class;
static t_symbol*    ps_globalsymbol_binding;
static t_symbol*    ps_globalsymbol_unbinding;
static t_symbol*    ps_buffer_modified;

/*************************************************************************************/
// Main() Function

extern "C" int TAP_EXPORT_MAXOBJ main(void)
{
    t_class *c;

    c = class_new("tap.buffer.peak~",(method)peak_new, (method)peak_free, sizeof(t_peak), (method)0L, A_SYM, 0);

    taptools_max::class_init(c);    common_symbols_init();
    class_addmethod(c, (method)peak_calc,        "bang",            A_LONG, 0);
    class_addmethod(c, (method)peak_set,        "set",            A_SYM, 0);
    class_addmethod(c, (method)peak_notify,        "notify",        A_CANT,    0);
    class_addmethod(c, (method)peak_assist,     "assist",        A_CANT, 0);
    class_addmethod(c, (method)stdinletinfo,    "inletinfo",    A_CANT, 0);

    class_dspinit(c);
    s_peak_class = taptools_max::class_finalize(c);

    ps_globalsymbol_binding = gensym("globalsymbol_binding");
    ps_globalsymbol_unbinding = gensym("globalsymbol_unbinding");
    ps_buffer_modified = gensym("buffer_modified");
}

/*************************************************************************************/
// Object Creation Routine

void *peak_new(t_symbol *s)
{
    t_peak *x = (t_peak *)taptools_max::instance_create(s_peak_class, taptools_max::PACKAGE_MSP + taptools_max::LICENSE_ARTIST + taptools_max::LICENSE_DEMO);
    if(x){
           object_obex_store((void *)x, _sym_dumpout, (object *)outlet_new(x,NULL));
        x->outlet = outlet_new(x, 0L);
        peak_set(x, s);
    }
    return (x);
}

void peak_free(t_peak *x)
{
    ;
}

/*************************************************************************************/
// Bound to input/inlet methods

t_max_err peak_notify(t_peak *x, t_symbol *s, t_symbol *msg, void *sender, void *data)
{
    if (msg == ps_globalsymbol_binding)
        x->buf = (t_buffer*)x->sym->s_thing;
    else if (msg == ps_globalsymbol_unbinding)
        x->buf = NULL;
    else if (msg == ps_buffer_modified)
        x->changed = true;

    return MAX_ERR_NONE;
}

// Set Buffer Method - (also used by DSP method)
void peak_set(t_peak *x, t_symbol *s)
{
    if(s != x->sym){
        x->buf = (t_buffer*)globalsymbol_reference((t_object*)x, s->s_name, "buffer~");
        if(x->sym)
            globalsymbol_dereference((t_object*)x, x->sym->s_name, "buffer~");
        x->sym = s;
        x->changed = true;
    }
}

// Method for Assistance Messages
void peak_assist(t_peak *x, void *b, long msg, long arg, char *dst)
{
    strcpy(dst, "bang to peakalize the buffer");
}

// peak point calculation
void peak_calc(t_peak *x)
{
    if(!x->changed)
        goto done;

    if(!x->buf){
        object_error((t_object *)x, "no buffer is bound to this object");
        return;
    }

    {
        t_buffer    *b = x->buf;            // Our Buffer
        float        *tab;                    // Will point to our buffer's values
        long        i, chan;
        double        current_samp = 0.0;        // current sample value

        ATOMIC_INCREMENT((int32_t*)&b->b_inuse);
        if (!x->buf->b_valid) {
            ATOMIC_DECREMENT((int32_t*)&b->b_inuse);
            return;
        }

        // FIND PEAK VALUE
        tab = b->b_samples;                // point tab to our sample values
        for(chan=0; chan < b->b_nchans; chan++){
            for(i=0; i < b->b_frames; i++){
                if(fabs(tab[(chan * b->b_nchans) + i]) > current_samp){
                    current_samp = fabs(tab[(chan * b->b_nchans) + i]);
                    x->index = (chan * b->b_nchans) + i;
                }
            }
        }

        ATOMIC_DECREMENT((int32_t*)&b->b_inuse);
    }

done:
    x->changed = false;
    outlet_int(x->outlet, x->index);
}

Peter Castine's icon

It's not unrealistic if you've got a reasonable command of C under your belt. Just tedious because there are a lot of (mostly tiny) methods to implement.

That said, my gut feeling is that a more "max-like" design would be a series of specific-purpose externs, rather than the Swiss Army Knife approach taken by [mxj buf.op]. Particularly if you're new to C, you might find it easier to get two or three simple objects working; then factor out the common code to a file linked into all your buffer externs; then build the rest. But some people may find that old-school.

For what it's worth, that's how I cut my teeth with external programming.

As for your long-term goal: I'm still not sure if any Max/MSP-based applications have made it into the AppStore. Someone correct me if I'm wrong (please, I'd love to be corrected on this!-)

Luigi Castelli's icon

Hi there,

I think that for the average programmer it should be a pretty easy project.

However, if you are just beginning with C, maybe you should start with simpler projects to get your chops up to speed and learn the details of the Max/MSP API, and when you feel confident enough dive into this Buf.op conversion project.

Having said that, here is some code that reverses the content of a buffer~.
(coded from the top of my head without running it, so there could be bugs)

void mybuf_reverse(t_mybuf *x, t_symbol *sym, long argc, t_atom *argv)
{
    t_buffer *b = x->buf;

        if (!b)
            return;

        ATOMIC_INCREMENT((int32_t *)&b->b_inuse);
    if (!b->b_valid) {
            ATOMIC_DECREMENT((int32_t*)&b->b_inuse);
        return;
        }
        float *tab = b->b_samples;
    long frames = b->b_frames;
    long nchans = b->b_nchans;
    long chan;

    for (chan = 0; chan < nchans; chan++) {

        float *beg = tab + chan;
        float *end = beg + frames - 1;

        while (beg < end) {

            float t = *beg;    // sample swap
        *beg = *end;
        *end = t;

        beg += nchans;
        end -= nchans;
        }
    }
        ATOMIC_DECREMENT((int32_t*)&b->b_inuse);
}

Hope this helps.

- Luigi

Luigi Castelli's icon

Yes, that's correct. The above code reverses the whole buffer~.
If you need an user defined range within the buffer~, you can change the *beg and *end pointers to run the while loop only on the user defined range. If you define the range in milliseconds, you may want to use the b_msr field in the t_buffer struct to calculate the beginning and ending samples.

Cheers.

- Luigi