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

    Jun 10 2011 | 7:42 am
    ? 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?

    • Jun 10 2011 | 5:36 pm
      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);
      	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);
      	   	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~");
      			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)
      		goto done;
      		object_error((t_object *)x, "no buffer is bound to this object");
      		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
      		if (!x->buf->b_valid) {
      		// 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;
      	x->changed = false;
      	outlet_int(x->outlet, x->index);
    • Jun 10 2011 | 7:10 pm
      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!-)
    • Jun 11 2011 | 12:42 am
      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)
              ATOMIC_INCREMENT((int32_t *)&b->b_inuse);
      	if (!b->b_valid) {
              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;
      Hope this helps.
      - Luigi
    • Jun 11 2011 | 7:56 am
      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.
      - Luigi