Gen~ why no bitwise operators like >> or bitwise logic ops?

    Oct 17 2011 | 11:45 am
    So I tried the new Max6 beta and was really disappointed to discover the lack of bitshift and any bitwise logic operators for the gen objects. These should really be the easiest elements to implement for gen~ right? And more importantly, they are a very valuable asset when doing low-level sound algorithms.
    I really hope you change your mind about this...

    • Oct 17 2011 | 12:40 pm
      Well - bitshift / bitwise operators are more commonly used on integer values, whereas within gen~ all signals are 64bit float. There are bitwise instruction sets for floating point, although I'm not sure I've ever seen a fp bitshift, as it would be pretty meaningless.
      I'm curious - what do you want them for? I do *A LOT* of low level DSP coding, and I rarely if ever use floating point bitwise ops - am I missing something here? The only real use for them (other than hardcore distorted sound munging) that springs to mind is for doing crazy efficiency hacks that really don't fit into the gen~ idea of the interface taking care of low-level implementation details.
      I'm genuinely curious, as to what they are valuable for...
    • Oct 17 2011 | 1:08 pm
      Cheap downsampling distortion effects in integer based audio. Personally, not a fan, but there are many fans of downsample artifacts.
    • Oct 17 2011 | 1:18 pm
      Sure - I get that (hence the munging comment), but you don't *need* a bitshift operator for that, you can multiply the audio up, truncate the floating point part, and then divide back down. In fact, that gives you a lot more options for pseudo-fractional bit depths.
      Now if there's no way to truncate that could be a more serious problem IMHO.
      I'm still interested to here from Veqtor about what he had in mind to do with bitwise stuff...
    • Oct 17 2011 | 1:26 pm
      Well, emulating old machines like atari 2600 and storing some information in upper bits etc
      For example building your own pseudo-random number generator. I thought the point of gen was to get access to low level blocks. You can for example do some quite nice glitchy visuals using xor and bitshift, it would be a shame if they're left out of the jitter equivalent...
      Okay, I can do it, but it feels wrong to build buckets of history to create a shift register...
    • Oct 17 2011 | 5:31 pm
      Just chiming in to say that I'm loving the mult/trunc/divide pseudo-fractional thing!
    • Oct 17 2011 | 11:41 pm
      Ironically, I spent some time in the last three months writing audio rate pseudo random stuff / MLS etc., so I should have thought of that! I have no idea of usefulness for jitter, as that is not so much my area, but I'm sure you are right - I can say however, that in jit.pix.gen (I think that's right) that all the calculations are floating point, so same issue is going to apply...
      Actually that's a much better argument for it than I was expecting, but there are probably a couple of things worth bearing in mind:
      1 - This is far lower level than a lot of people are going to want to go (or even be aware of).
      2 - Really I can only seeing it make sense if there is a way to do specifically 32-bit int calculations within gen (perhaps in a specific gen object within there are a new set of int objects??) - the confusion of mixing 64bit floats and ints is just not a great idea, or going back and forth between the two...
      It's not up to me though. I'd guess this won't come anytime soon, but let's see what happens!
      @Rodrigo -good...
    • Oct 18 2011 | 7:58 am
      I guess I will have to implement what I want to do with history objects then. Too bad, I did the same in reaktor until a friend of mine showed me how to do it with bitshifting, bitmasking, which took the cpu usage down from 23 to 1%...
      Even though things like these are only useful for optimization I still believe the gen object doesn't magically optimize our code, the same optimization principles of ordinary programming still probably applies.
      Perhaps Max could benefit from assignable datatypes inside gen like int, boolean and float w. 32 and 64 bit precision? Reaktor has this in core and it's quite useful given what a terrible waste it is to 64-bit floats to store bools.
    • Oct 18 2011 | 8:45 am
      I think a 'strongly-typed' [gen~] would cause more trouble than it's worth. It would need duplicate operators for each type, and you'd have to build your patches in a modular way so as to minimise casting. And once you start wrapping casts in higher-level stuff like [gen~], you're wasting a lot of cycles.
      Perhaps it will be possible to write our own [gen~] operators? That would really be something. But if you crave the fastest performance possible I reckon the minimum overhead comes from writing your own MSP objects.
      It sounds like you're already low-level enough to handle a bit of C. Consider taking it to the next level like this (XCode/Intel Mac syntax):
      float *in = (t_float *)(w[1]);  // Object input
      float *out = (t_float *)(w[2]); // Object output
      long rounded;                   // Temporary integers for bit-twiddling
      long truncated;
      asm {
              mov     esi,    in      // Load pointer to input signal into esi
              fldl    [esi]           // Copy qword from address pointed to by esi
                                      // onto the FPU stack
              fist    rounded         // Convert to integer using default rounding
                                      // mode (usually round to nearest) and store
                                      // in our temporary variable
              fisttp  truncated       // As above, but with truncation and pop
                                      // the FPU stack (requires SSE3)
      asm {
              mov     edi,    out     // Load pointer for output signal into edi
              fild    rounded         // Convert integer back to floating point
              fstpl   [edi]           // Output a double-precision value and pop
                                      // the FPU stack.
    • Oct 18 2011 | 9:37 am
      Oh, how silly of me. You probably just want to deal with bitwise representations of signals as floats, rather than casting them to ints.
      In any case, if you're dealing with signals in the range +/- 1.0, you'll need to multiply them by 1<
      Watch out for negative integers - they will be stored in two's complement form. There are also a couple of other pitfalls with fixed point; here is a good guide:
      long multiplier = 1 << 63;
      asm {
              fild    multiplier
              mov     esi,    in      // Load pointer to input signal into esi
              fldl    [esi]           // Copy qword from address pointed to by esi
                                      // onto the FPU stack
              fmul    st(0),  st(1)   // Upscale for 1:63 fixed-point arithmetic
              fist    rounded         // Convert to integer using default rounding
                                      // mode (usually round to nearest) and store
                                      // in our temporary variable
      //        fstp    st(0)           // UNCOMMENT ME IF YOU WANT TO USE C
      //}                               // UNCOMMENT ME IF YOU WANT TO USE C
      //asm {                           // UNCOMMENT ME IF YOU WANT TO USE C
      //        fild    multiplier      // UNCOMMENT ME IF YOU WANT TO USE C
              fild    rounded         // Convert integer back to floating point
              fdiv    st(0), st(1)    // Downscale from fixed-point representation
              mov     edi,    out     // Load pointer for output signal into edi
              fstpl   [edi]           // Output a double-precision value and pop
                                      // the FPU stack.
              fstp    st(0)
      If you just want to manipulate floats at the bit-level instead of worrying about fixed-point arithmetic, if you do play with any assembly, remember that 64-bit MSP signals won't fit in a 32-bit register! Hopefully Max will go truly 64-bit and we'll have access to 16 128-bit XMM registers, 16 64-bit GP registers, and even 256-bit YMM registers for Sandy Bridge peeps.
    • Oct 18 2011 | 3:18 pm
      I realise this is getting rather OT, seeing as we don't have a [gen~] SDK, but in case it helps you optimise your patch in the MSP domain, here's an idea how to implement some bitwise stuff using SSE in assembly:
      float *in1 = (t_float *)(ins[0]);
      float *in2 = (t_float *)(ins[1]);
      float *out = (t_float *)(outs[0]);
      int n = sampleframes;
      asm {
              mov     esi,    in1     // esi = &in1
              mov     edi,    in2     // edi = &in2
              mov     ebx,    out     // ebx = &out
              mov     ecx,    n       // ecx = n
              shr     ecx,    3       // ecx >>= 3 (we process 8 doubles per loop)
              // fill the first 4 xmm registers with in1[0] to in1[7]
              movapd  xmm0,   [esi]
              movapd  xmm1,   [esi + 16]
              movapd  xmm2,   [esi + 32]
              movapd  xmm3,   [esi + 48]
              // fill the last 4 xmm registers with in2[0] to in2[7]
              movapd  xmm4,   [edi]
              movapd  xmm5,   [edi + 16]
              movapd  xmm6,   [edi + 32]
              movapd  xmm7,   [edi + 48]
              add     esi,    64  // a bit of pointer arithmetic
              // There now follow a few little blocks demonstrating
              // bitwise operations on packed double-precision values.
              // Uncomment ONE block only to perform the desired operation.
              // in1 = in1 AND in2 //
      //        andpd   xmm0,   xmm4
      //        andpd   xmm1,   xmm5
      //        andpd   xmm2,   xmm6
      //        andpd   xmm3,   xmm7
              // in1 = in1 OR in2 //
      //        orpd    xmm0,   xmm4
      //        orpd    xmm1,   xmm5
      //        orpd    xmm2,   xmm6
      //        orpd    xmm3,   xmm7
              // in1 = in1 XOR in2 //
      //        xorpd   xmm0,   xmm4
      //        xorpd   xmm1,   xmm5
      //        xorpd   xmm2,   xmm6
      //        xorpd   xmm3,   xmm7
              // in1 = in1 AND (NOT in2) //
      //        andnpd  xmm0,   xmm4
      //        andnpd  xmm1,   xmm5
      //        andnpd  xmm2,   xmm6
      //        andnpd  xmm3,   xmm7
              add     edi,    64  // a bit more pointer arithmetic
              // save the results in out[0] to out[7]
              movntpd [ebx],      xmm0
              movntpd [ebx + 16], xmm1
              movntpd [ebx + 32], xmm2
              movntpd [ebx + 48], xmm3
              add     ebx,    64  // last bit of pointer arithmetic
              sub     ecx,    1   // decrement loop counter
              jnz     loopStart   // jump if not zero (loop)
    • Oct 21 2011 | 11:55 am
      I didn't mean to kill this thread with all the unneccessary crap. I'd just like to say, having tried to convert some of my more complex MSP patches to [gen~], that the lack of bitwise operators is hitting me hard too.
      They're useful for far more than just audio effects - I use them for signal logic in sequencing patches. The [?] operator, while very useful, doesn't always cut the mustard, and I'm finding myself having to use some very clunky workarounds.
    • Jan 21 2014 | 5:22 pm
      hi, i'd really need a bitwise AND operator for audio rate signals in Max. Do you know any external? Seems that gen~ does not have one. Should i try to build my own external?
    • Jan 21 2014 | 5:59 pm
      There are existing externals that do this sort of thing already. The [distort~] object is one example, although I'm fond of [lp.nn~], which allows fractional "bit shifts" (since we're in floating-point land, there's actually no need to limit oneself to integer bit shifts, once someone's got his or her head around the math for you).
      Bit shifts are simply multiplication and/or division by powers of two, with a bit of rounding. Getting what you want is, to paraphrase Bill Clinton, "just arithmetic." Surely you're not worried about an additional cycle or two of processor time nowadays?
    • Jan 21 2014 | 6:09 pm
      @Alfonso: similarly, if you're wanting an AND operator in order to truncate bit precision, that's just multiply, truncate to integer, and divide.
      Bit masking with arbitrary masks isn't so easy in floating point, but, with rather few exotic exceptions, that doesn't make a lot of sense with fp. I'm not sure that it makes much more sense with integers, for that matter.
      I can't get over the feeling that those wanting bit-munging operators aren't really thinking floating-point audio signal representation. Bit munging and fp are two different worlds (with rather few exotic exceptions).
    • Jan 21 2014 | 8:33 pm
      thanks Peter, but this is what i'm trying to do a kind of bit mask bit reducer as in this reaktor tutorial
    • Jan 22 2014 | 11:07 am
      Your patch does what I was trying to explain in my previous message.
      Doing this in gen~ may be just as time-consuming as what you've done in Max.
      This is all (relatively) a piece of cake in a C-based external. The hardest part would be designing a convenient interface that is sufficiently flexible. (Ideally the external wouldn't lock you into an 8-bit model; bit munging with 16-, 24-, and even 32-bit representations is probably desirable.)
      The thing with gen~ is that the bit-munging operators on their own won't do the job; you also need the ability to convert the floating-point signal to a (fixed-point) integer representation if you want to have the efficiency of bit operations. The latter's not hard, but it's a bit more than just multiplying by a constant. Until both capabilities are in place, you'll need to do all that multiplying and rounding and adding and dividing.
      I might actually get something like this into an external (possibly as an extension to the lp.nn~ distortion thing). Alas, my to-do list is longer than I'd like to admit, no telling when this idea would percolate up into reality:-(
    • Jan 22 2014 | 3:55 pm
      The OTO bicuit model i have here uses vanilla max. [bitand~] is where you need to go. it's ripped from this maxforlive device:
      hope this helps.
    • Jan 23 2014 | 12:43 pm
      Thanks Peter, and thanks Wetterberg for the great tip!