Gen~ why no bitwise operators like >> or bitwise logic ops?
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...
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...
Alex
Cheap downsampling distortion effects in integer based audio. Personally, not a fan, but there are many fans of downsample artifacts.
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...
Alex
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...
Just chiming in to say that I'm loving the mult/trunc/divide pseudo-fractional thing!
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...
A.
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.
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)
}
// MORE C OR ASM BIT TWIDDLING GOES HERE
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.
}
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
// ASM ONLY IN HERE, BECAUSE WE CAN'T TRUST THE C COMPILER NOT TO
// TRASH THE FPU STACK. MULTIPLIER IS LEFT IN ST(0), TAKE CARE!
// fstp st(0) // UNCOMMENT ME IF YOU WANT TO USE C
//} // UNCOMMENT ME IF YOU WANT TO USE C
// ALTERNATIVELY, PUT YOUR C CODE HERE
//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.
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)
loopStart:
// 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)
}
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.
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?
thanks
a.
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?
@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).
thanks Peter, but this is what i'm trying to do
a kind of bit mask bit reducer as in this reaktor tutorial
http://www.youtube.com/watch?v=ohYQe5kzZg8
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:-(
The OTO bicuit model i have here uses vanilla max. [bitand~] is where you need to go.
it's ripped from this maxforlive device:
http://www.maxforlive.com/library/device/1372/fp-8bits
hope this helps.
Thanks Peter, and thanks Wetterberg for the great tip!
a.