msp external problem


    Aug 23 2006 | 12:47 am
    Hi,
    Having written a few max objects in C, I've started my first msp object. I am working with Visual Studio .NET 2003 under Windows XP. I am using Max 4.5.5.
    To take some first steps, I copied the sample plussz~ Visual Studio project (which compiles and works fine), and exchanged the code in plussz~.c for the code at the end of the e-mail. I have found that if the line
    x->ltestvar = 0;
    in the amstring_new function is removed, the external works fine, passing the audio through unchanged. But with this line, it does not work. If you try to place the external in a patch it does not give the object any inputs or outputs. However, it does not say 'No such object' in the Max window.
    Does anyone have any idea why this might be the case?
    Thanks,
    Aengus.
    #include "ext.h" #include "z_dsp.h"
    void* amstring_class;
    typedef struct _amstring { t_pxobject* x_obj; long ltestvar; } t_amstring;
    void amstring_dsp(t_amstring *x, t_signal **sp, short *count); t_int *amstring_perform(t_int *w); void* amstring_new(long delay, float feedback);
    void main(void) { setup((t_messlist **)&amstring_class, (method)amstring_new, (method)dsp_free, (short)sizeof(t_amstring), 0L, A_DEFLONG, A_DEFFLOAT, A_NOTHING); addmess((method)amstring_dsp, "dsp", A_CANT, 0); dsp_initclass(); // must call this function for MSP object classes }
    void amstring_dsp(t_amstring *x, t_signal **sp, short *count) { dsp_add(amstring_perform, 4, sp[0]->s_vec, sp[1]->s_vec, x, sp[0]->s_n); }
    t_int *amstring_perform(t_int* w) // 4 args. in memory pointed to by w. { t_float *in = (t_float *)(w[1]); t_float *out = (t_float *)(w[2]); t_amstring *x = (t_amstring *)(w[3]); int n = (int)(w[4]);
    while (--n) *++out = *++in; // copy input to output
    return (w+5); }
    void* amstring_new(long delay_length, float feedback_gain) { t_amstring* x = (t_amstring *)newobject(amstring_class); dsp_setup((t_pxobject *)x, 1); outlet_new((t_pxobject *)x, "signal");
    x->ltestvar = 0; // external works if this line is removed
    return(x); }
    ____________________ www.am-process.org

    • Aug 23 2006 | 1:04 am
      Hi Aengus,
      Look here:
      typedef struct _amstring { t_pxobject* x_obj; long ltestvar; } t_amstring;
      I don't think x_obj should be a pointer to a t_pxobject, but rather an actual t_pxobject instance. so change it to
      typedef struct _amstring { t_pxobject x_obj; long ltestvar; } t_amstring;
      and see if that works out.
      Ben
    • Aug 23 2006 | 1:04 am
      It looks like you have half of a pre-dec/incr approach to passing your vectors (which I recommend avoiding), which could potentially lead to a crash. Perhaps your DSP line should instead be:
      while (n--) *out++ = *in++; // copy input to output
      Good luck,
      Eric
    • Aug 23 2006 | 9:57 am
      Thanks to you both.
      That pointer which was the culprit might have taken a long time to find. Usually a typo like that causes a problem compiling (so you find them soon after you make them), so I guess I was somewhat unlucky to get as far as I did with that mistake in there.
      I was a little uncomfortable with the line which passes the vectors myself. I had copied it straight from the plussz~ example, just to get something working.
      Cheers,
      Aengus.
      On 8/23/06, Eric Lyon wrote: > It looks like you have half of a pre-dec/incr approach to passing your vectors > (which I recommend avoiding), which could potentially lead to a crash. Perhaps > your DSP line should instead be: > > while (n--) *out++ = *in++; // copy input to output > > Good luck, > > Eric > > > Quoting Aengus Martin : > > > Hi, > > > > Having written a few max objects in C, I've started my first msp > > object. I am working with Visual Studio .NET 2003 under Windows XP. I > > am using Max 4.5.5. > > > > To take some first steps, I copied the sample plussz~ Visual Studio > > project (which compiles and works fine), and exchanged the code in > > plussz~.c for the code at the end of the e-mail. I have found that if > > the line > > > > x->ltestvar = 0; > > > > in the amstring_new function is removed, the external works fine, > > passing the audio through unchanged. But with this line, it does not > > work. If you try to place the external in a patch it does not give the > > object any inputs or outputs. However, it does not say 'No such > > object' in the Max window. > > > > Does anyone have any idea why this might be the case? > > > > Thanks, > > > > Aengus. > > > > > > #include "ext.h" > > #include "z_dsp.h" > > > > void* amstring_class; > > > > typedef struct _amstring > > { > > t_pxobject* x_obj; > > long ltestvar; > > } t_amstring; > > > > void amstring_dsp(t_amstring *x, t_signal **sp, short *count); > > t_int *amstring_perform(t_int *w); > > void* amstring_new(long delay, float feedback); > > > > void main(void) > > { > > setup((t_messlist **)&amstring_class, (method)amstring_new, > > (method)dsp_free, > > (short)sizeof(t_amstring), 0L, A_DEFLONG, A_DEFFLOAT, A_NOTHING); > > addmess((method)amstring_dsp, "dsp", A_CANT, 0); > > dsp_initclass(); // must call this function for MSP object classes > > } > > > > void amstring_dsp(t_amstring *x, t_signal **sp, short *count) > > { > > dsp_add(amstring_perform, 4, sp[0]->s_vec, sp[1]->s_vec, x, sp[0]->s_n); > > } > > > > t_int *amstring_perform(t_int* w) // 4 args. in memory pointed to by w. > > { > > t_float *in = (t_float *)(w[1]); > > t_float *out = (t_float *)(w[2]); > > t_amstring *x = (t_amstring *)(w[3]); > > int n = (int)(w[4]); > > > > while (--n) *++out = *++in; // copy input to output > > > > return (w+5); > > } > > > > void* amstring_new(long delay_length, float feedback_gain) > > { > > t_amstring* x = (t_amstring *)newobject(amstring_class); > > dsp_setup((t_pxobject *)x, 1); > > outlet_new((t_pxobject *)x, "signal"); > > > > x->ltestvar = 0; // external works if this line is removed > > > > return(x); > > } > > > > > > ____________________ > > www.am-process.org > > > > > >
      -- ____________________ www.am-process.org
    • Aug 23 2006 | 11:30 am
      Both Ben and Eric are right, but there's more.
      > typedef struct _amstring > { > t_pxobject* x_obj; recto: t_pxobject x_obj;
      The SDK underscores that the first member of your object must be the whole entire t_pxobject struct, not a pointer.
      > long ltestvar; > } t_amstring; > > void amstring_dsp(t_amstring *x, t_signal **sp, short *count); > t_int *amstring_perform(t_int *w); > void* amstring_new(long delay, float feedback); > > void main(void) > { > setup((t_messlist **)&amstring_class, (method)amstring_new, > (method)dsp_free, > (short)sizeof(t_amstring), 0L, A_DEFLONG, A_DEFFLOAT, A_NOTHING);
      Consider yourself lucky that Max isn't already croaking here. In your declaration, sizeof(t_amstring) is 8, but the smallest possible Max object must be at least 16 bytes in size.
      > addmess((method)amstring_dsp, "dsp", A_CANT, 0); > dsp_initclass(); // must call this function for MSP object classes > } > > void amstring_dsp(t_amstring *x, t_signal **sp, short *count) > { > dsp_add(amstring_perform, 4, sp[0]->s_vec, sp[1]->s_vec, x, sp[0]- > >s_n); > }
      The parameters to dsp_add() must match what you're doing in your perform method, which they don't. At this stage of the game, you're better off to leave the dsp_add() call as is and modify your perform method. See below.
      > > t_int *amstring_perform(t_int* w) // 4 args. in memory pointed to > by w. > { > t_float *in = (t_float *)(w[1]); > t_float *out = (t_float *)(w[2]); > t_amstring *x = (t_amstring *)(w[3]); > int n = (int)(w[4]); > > while (--n) *++out = *++in; // copy input to output
      recto: while (n--) *out++ = *in++;
      Some of the SDK examples and documentation use predec and/or preinc instead of the postfix ops, but look *very* carefully at how they call dsp_add()! Objects that do this sort of stuff need to tweak the dsp_add() parameters to compensate for the fact that the main loop is "one too few" and that the read/write vectors "start one too early". This is easily overlooked when reading the SDK examples.
      Some processors can, indeed, shave a few cycles off by using predec instead of postdec. But there are other, less error-prone ways of doing this, such as:
      do { *out++ = *in++; } while (n-- > 0);
      This requires that the initial value for n be guaranteed to be strictly positive, which is more than a reasonable presumption for the size of an MSP signal vector.
      In either case, I find
      while (n-- > 0)
      far more readable for the loop condition, and most compilers will generate identical machine code with or without the explicit less- than-zero. But check your disassembly code if it worries you.
      > return (w+5); > } > > void* amstring_new(long delay_length, float feedback_gain) > { > t_amstring* x = (t_amstring *)newobject(amstring_class); > dsp_setup((t_pxobject *)x, 1); > outlet_new((t_pxobject *)x, "signal"); > > x->ltestvar = 0; // external works if this line is removed
      This is writing four bytes at offset 4 into the t_pxobject struct that newobject() and dsp_setup() so carefully initialized. Max finally crashes.
      The offset at 4 bytes is actually the o_magic member of the basic t_object struct at the heart of every Max object. The Max engine checks this member at certain points to make sure that valid objects are being passed around. Overwriting this value behind Max' back is a good way to wreck havoc. As you have seen.
      > > return(x); > }
      This is just a style thing, but I don't know why people insist upon writing return statements as if they were functions. The parentheses are completely superfluous. They don't actually hurt anything; so don't worry, just be aware that they're superfluous.-
      return x;
      -- P
      -------------- http://www.bek.no/~pcastine/Litter/ ------------- Peter Castine +--> Litter Power & Litter Bundle for Jitter Universal Binaries on the way iCE: Sequencing, Recording & Interface Building for |home | chez nous| Max/MSP Extremely cool |bei uns | i nostri| http://www.dspaudio.com/ http://www.castine.de
    • Aug 27 2006 | 5:05 am
      Hi,
      Peter, thanks for the pointing those issues out- I think I've addressed all of them now.
      In fact the external seems to work exactly as I want it to except for one new problem which I don't understand at all. I've included a patcher in the email and a windows binary of the external is attached. It may not be appropriate to attach a binary like that, but it is tiny and it should not be necessary to use it to understand the issue- only to demonstrate it.
      The patcher contains a click~ object, which sends audio along two signal chains, each of which contains an instance of my am-string~ object. One chain ends at dac~ 1 and the other at dac~ 2. The only difference between the two is that the *~ 0.9 in the right hand one is connected to another *~ object as well as to an instance of the am-string~ object. This extra *~ object is not connected to anything.
      If you click the bang, which sends a click down both signal chains, there is only output on the right hand chain. This is the one which has the extra *~ object which, by my understanding of the way max works, should not have any effect on anything.
      Can someone please explain? (I'll post the current source code for the object if it'd help)
      Thanks,
      Aengus.
      On 8/23/06, Peter Castine wrote: > Both Ben and Eric are right, but there's more. > > > typedef struct _amstring > > { > > t_pxobject* x_obj; > recto: t_pxobject x_obj; > > The SDK underscores that the first member of your object must be the > whole entire t_pxobject struct, not a pointer. > > > long ltestvar; > > } t_amstring; > > > > void amstring_dsp(t_amstring *x, t_signal **sp, short *count); > > t_int *amstring_perform(t_int *w); > > void* amstring_new(long delay, float feedback); > > > > void main(void) > > { > > setup((t_messlist **)&amstring_class, (method)amstring_new, > > (method)dsp_free, > > (short)sizeof(t_amstring), 0L, A_DEFLONG, A_DEFFLOAT, A_NOTHING); > > Consider yourself lucky that Max isn't already croaking here. In your > declaration, sizeof(t_amstring) is 8, but the smallest possible Max > object must be at least 16 bytes in size. > > > addmess((method)amstring_dsp, "dsp", A_CANT, 0); > > dsp_initclass(); // must call this function for MSP object classes > > } > > > > void amstring_dsp(t_amstring *x, t_signal **sp, short *count) > > { > > dsp_add(amstring_perform, 4, sp[0]->s_vec, sp[1]->s_vec, x, sp[0]- > > >s_n); > > } > > The parameters to dsp_add() must match what you're doing in your > perform method, which they don't. At this stage of the game, you're > better off to leave the dsp_add() call as is and modify your perform > method. See below. > > > > > t_int *amstring_perform(t_int* w) // 4 args. in memory pointed to > > by w. > > { > > t_float *in = (t_float *)(w[1]); > > t_float *out = (t_float *)(w[2]); > > t_amstring *x = (t_amstring *)(w[3]); > > int n = (int)(w[4]); > > > > while (--n) *++out = *++in; // copy input to output > > recto: while (n--) *out++ = *in++; > > Some of the SDK examples and documentation use predec and/or preinc > instead of the postfix ops, but look *very* carefully at how they > call dsp_add()! Objects that do this sort of stuff need to tweak the > dsp_add() parameters to compensate for the fact that the main loop > is "one too few" and that the read/write vectors "start one too > early". This is easily overlooked when reading the SDK examples. > > Some processors can, indeed, shave a few cycles off by using predec > instead of postdec. But there are other, less error-prone ways of > doing this, such as: > > do { *out++ = *in++; } while (n-- > 0); > > This requires that the initial value for n be guaranteed to be > strictly positive, which is more than a reasonable presumption for > the size of an MSP signal vector. > > In either case, I find > > while (n-- > 0) > > far more readable for the loop condition, and most compilers will > generate identical machine code with or without the explicit less- > than-zero. But check your disassembly code if it worries you. > > > return (w+5); > > } > > > > void* amstring_new(long delay_length, float feedback_gain) > > { > > t_amstring* x = (t_amstring *)newobject(amstring_class); > > dsp_setup((t_pxobject *)x, 1); > > outlet_new((t_pxobject *)x, "signal"); > > > > x->ltestvar = 0; // external works if this line is removed > > This is writing four bytes at offset 4 into the t_pxobject struct > that newobject() and dsp_setup() so carefully initialized. Max > finally crashes. > > The offset at 4 bytes is actually the o_magic member of the basic > t_object struct at the heart of every Max object. The Max engine > checks this member at certain points to make sure that valid objects > are being passed around. Overwriting this value behind Max' back is a > good way to wreck havoc. As you have seen. > > > > > return(x); > > } > > This is just a style thing, but I don't know why people insist upon > writing return statements as if they were functions. The parentheses > are completely superfluous. They don't actually hurt anything; so > don't worry, just be aware that they're superfluous.- > > return x; > > > -- P > > > > -------------- http://www.bek.no/~pcastine/Litter/ ------------- > Peter Castine +--> Litter Power & Litter Bundle for Jitter > Universal Binaries on the way > iCE: Sequencing, Recording & > Interface Building for |home | chez nous| > Max/MSP Extremely cool |bei uns | i nostri| > http://www.dspaudio.com/ http://www.castine.de > > >
      -- ____________________ www.am-process.org