Branchless Clipping

Type : Clipping at 0dB, with none of the usual 'if..then..'
References : Posted by musicdsp[AT]dsparsons[DOT]co[DOT]uk
Notes :
I was working on something that I wanted to ensure that the signal never went above 0dB, and a branchless solution occurred to me.

It works by playing with the structure of a single type, shifting the sign bit down to make a new mulitplicand.

calling MaxZerodB(mydBSample) will ensure that it will never stray over 0dB.
By playing with signs or adding/removing offsets, this offers a complete branchless limiting solution, no matter whether dB or not (after all, they're all just numbers...).

eg:
Limit to <=0 : sample:=MaxZerodB(sample);
Limit to <=3 : sample:=MaxZerodB(sample-3)+3;
Limit to <=-4 : sample:=MaxZerodB(sample+4)-4;

Limit to >=0 : sample:=-MaxZerodB(-sample);
Limit to >=2 : sample:=-MaxZerodB(-sample+2)+2;
Limit to >=-1.5: sample:=-MaxZerodB(-sample-1.5)-1.5;

Whether it actually saves any CPU cycles remains to be seen, but it was an interesting diversion for half an hour :)

[Translating from pascal to other languages shouldn't be too hard, and for doubles, you'll need to fiddle it abit :)]
Code :
function MaxZerodB(dBin:single):single;
var tmp:longint;
begin
     //given that leftmost bit of a longint indicates the negative,
     //  if we shift that down to bit0, and multiply dBin by that
     //  it will return dBin, or zero :)
     tmp:=(longint((@dBin)^) and $80000000) shr 31;
     result:=dBin*tmp;
end;

Comments
from : hotpop[DOT]com[AT]blargg
comment : Since most processors include a sign-preserving right shift, you can right shift by 31 to end up with either -1 (all bits set) or 0, then mask the original value with it: out = (in >> 31) & in;

from : mumart[AT]gmail[DOT]com
comment : I prefer this method, using a sign-preserving shift, as it can clip a signal to arbitrary bounds: over = upper_limit - samp mask = over >> 31 over = over & mask samp = samp + over over = samp - lower_limit mask = over >> 31 over = over & mask samp = samp - over Is it faster? Maybe on modern machines with 20-plus-stage pipelines and if the signal is clipped often, as the branches are not predictable.

from : musicdsp[AT]dsparsons[DOT]co[DOT]uk
comment : hmm.. Did some looking into the sign preserving thing. My laptop has an P3 which didn't preserve as mentioned, and my work PC (P4HT) didn't either. Maybe its an AMD or motorola thing :) unless it's how delphi interprets the shr.. what does a C++ compiler generate for '>>' ?

from : mumart[AT]gmail[DOT]com
comment : C and C++ have sign-preserving shifts. If the value is negative, a right shift will add ones onto the left hand side (thus -2 becomes -1 etc). Java also has a non-sign-preserving right shift operator (>>>). I tried googling for information on how Delphi handles shifts, but nothing turned up. Looks like you might need to use in-line assembly :/

from : bero[AT]0ok[DOT]de
comment : Here my SAR function for Delphi+FreePascal FUNCTION SAR(Value,Shift:INTEGER):INTEGER; {$IFDEF CPU386}ASSEMBLER; REGISTER;{$ELSE}{$IFDEF FPC}INLINE;{$ELSE}REGISTER;{$ENDIF}{$ENDIF} {$IFDEF CPU386} ASM MOV ECX,EDX SAR EAX,CL END; {$ELSE} BEGIN RESULT:=(Value SHR Shift) OR (($FFFFFFFF+(1-((Value AND (1 SHL 31)) SHR 31) AND ORD(Shift<>0))) SHL (32-Shift)); END; {$ENDIF}

from : bero[AT]0ok[DOT]de
comment : Ny branchless clipping functions (the first is faster than the second) FUNCTION Clip(Value,Min,Max:SINGLE):SINGLE; ASSEMBLER; STDCALL; CONST Constant0Dot5:SINGLE=0.5; ASM FLD DWORD PTR Value FLD DWORD PTR Min FLD DWORD PTR Max FLD ST(2) FSUB ST(0),ST(2) FABS FADD ST(0),ST(2) FADD ST(0),ST(1) FLD ST(3) FSUB ST(0),ST(2) FABS FSUBP ST(1),ST(0) FMUL DWORD PTR Constant0Dot5 FFREE ST(4) FFREE ST(3) FFREE ST(2) FFREE ST(1) END; FUNCTION ClipDSP(Value:SINGLE):SINGLE; {$IFDEF CPU386} ASSEMBLER; REGISTER; ASM MOV EAX,DWORD PTR Value AND EAX,$80000000 AND DWORD PTR Value,$7FFFFFFF FLD DWORD PTR Value FLD1 FSUBP ST(1),ST(0) FSTP DWORD PTR Value MOV EDX,DWORD PTR Value AND EDX,$80000000 SHR EDX,31 NEG EDX AND DWORD PTR Value,EDX FLD DWORD PTR Value FLD1 FADDP ST(1),ST(0) FSTP DWORD PTR Value OR DWORD PTR Value,EAX FLD DWORD PTR Value END; {$ELSE} VAR ValueCasted:LONGWORD ABSOLUTE Value; Sign:LONGWORD; BEGIN Sign:=ValueCasted AND $80000000; ValueCasted:=ValueCasted AND $7FFFFFFF; Value:=Value-1; ValueCasted:=ValueCasted AND (-LONGWORD((ValueCasted AND $80000000) SHR 31)); Value:=Value+1; ValueCasted:=ValueCasted OR Sign; RESULT:=Value; END; {$ENDIF}