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


Anonymous

Anonymous
Jun 10, 2011 at 7:42am

? 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?

#57545
Jun 10, 2011 at 5:36pm

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);
}
#206183
Jun 10, 2011 at 7:10pm

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!-)

#206184
Jun 11, 2011 at 12:42am

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

#206185
Jun 11, 2011 at 7:56am

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

#206186

You must be logged in to reply to this topic.