Phaser code

References : Posted by Ross Bencina
Linked file : phaser.cpp
(this linked file is included below)
Notes :
(see linked file)
Comments
from : dj_ikke[AT]hotmail[DOT]com
comment : What range should be the float parameter of the Update function? From -1 to 1, 0 to 1 or -32768 to 32767?

from : rossb[AT]audiomulch[DOT]com
comment : It doesn't matter what the range of the parameter to the Update function is. Usually in a floating point signal chain you would use -1 to 1,but anything else will work just as well.

from : askywhale[AT]free[DOT]fr
comment : Please what is the usual range of frequencies ?

from : Christian[AT]savioursofsoul[DOT]de
comment : Delphi / Object Pascal Translation: ----------------------------------- unit Phaser; // Still not efficient, but avoiding denormalisation. interface type TAllPass=class(TObject) private fDelay : Single; fA1, fZM1 : Single; fSampleRate : Single; procedure SetDelay(v:Single); public constructor Create; destructor Destroy; override; function Process(const x:single):single; property SampleRate : Single read fSampleRate write fSampleRate; property Delay: Single read fDelay write SetDelay; end; TPhaser=class(TObject) private fZM1 : Single; fDepth : Single; fLFOInc : Single; fLFOPhase : Single; fFeedBack : Single; fRate : Single; fMinimum: Single; fMaximum: Single; fMin: Single; fMax: Single; fSampleRate : Single; fAllpassDelay: array[0..5] of TAllPass; procedure SetSampleRate(v:Single); procedure SetMinimum(v:Single); procedure SetMaximum(v:Single); procedure SetRate(v:Single); procedure Calculate; public constructor Create; destructor Destroy; override; function Process(const x:single):single; property SampleRate : Single read fSampleRate write SetSampleRate; property Depth: Single read fDepth write fDepth; //0..1 property Feedback: Single read fFeedback write fFeedback; // 0..<1 property Minimum: Single read fMin write SetMinimum; property Maximum: Single read fMax write SetMaximum; property Rate: Single read fRate write SetRate; end; implementation uses DDSPUtils; const kDenorm=1E-25; constructor TAllpass.Create; begin inherited; fA1:=0; fZM1:=0; end; destructor TAllpass.Destroy; begin inherited; end; function TAllpass.Process(const x:single):single; begin Result:=x*-fA1+fZM1; fZM1:=Result*fA1+x; end; procedure TAllpass.SetDelay(v:Single); begin fDelay:=v; fA1:=(1-v)/(1+v); end; constructor TPhaser.Create; var i : Integer; begin inherited; fSampleRate:=44100; fFeedBack:=0.7; fLFOPhase:=0; fDepth:=1; fZM1:=0; Minimum:=440; Maximum:=1600; Rate:=5; for i:=0 to Length(fAllpassDelay)-1 do fAllpassDelay[i]:=TAllpass.Create; end; destructor TPhaser.Destroy; var i : Integer; begin for i:=0 to Length(fAllpassDelay)-1 do fAllpassDelay[i].Free; inherited; end; procedure TPhaser.SetRate(v:Single); begin fLFOInc:=2*Pi*(v/SampleRate); end; procedure TPhaser.Calculate; begin fMin:= fMinimum / (fSampleRate/2); fMax:= fMinimum / (fSampleRate/2); end; procedure TPhaser.SetMinimum(v:Single); begin fMinimum:=v; Calculate; end; procedure TPhaser.SetMaximum(v:Single); begin fMaximum:=v; Calculate; end; function TPhaser.Process(const x:single):single; var d: Single; i: Integer; begin //calculate and update phaser sweep lfo... d := fMin + (fMax-fMin) * ((sin( fLFOPhase )+1)/2); fLFOPhase := fLFOPhase + fLFOInc; if fLFOPhase>=Pi*2 then fLFOPhase:=fLFOPhase-Pi*2; //update filter coeffs for i:=0 to 5 do fAllpassDelay[i].Delay:=d; //calculate output Result:= fAllpassDelay[0].Process( fAllpassDelay[1].Process( fAllpassDelay[2].Process( fAllpassDelay[3].Process( fAllpassDelay[4].Process( fAllpassDelay[5].Process(kDenorm + x + fZM1 * fFeedBack )))))); fZM1:=tanh2a(Result); Result:=tanh2a(1.4*(x + Result * fDepth)); end; procedure TPhaser.SetSampleRate(v:Single); begin fSampleRate:=v; end; end.

from : Christian[AT]savioursofsoul[DOT]de
comment : Ups, forgot to remove my special, magic incredients "tanh2a(1.4*(". It's just to make the sound even warmer. The frequency range i used for Minimum and Maximum is 0..22000. But I believe there is still an error in that formula. The input range doesn't matter (if you remove my special incredient), because it is a linear system.

from : thaddy[AT]thaddy[DOT]com
comment : I thought I already posted this but here's my interpretation for Delphi and KOL. The reason I repost this, is that it is rather efficient and has no denormal problems. unit Phaser; { Unit: Phaser purpose: Phaser is a six stage phase shifter, intended to reproduce the sound of a traditional analogue phaser effect. Author: Thaddy de Koning, based on a musicdsp.pdf C++ Phaser by Ross Bencina.http://www.musicdsp.org/musicdsp.pdf Copyright: This version (c) 2003, Thaddy de Koning Copyrighted Freeware Remarks: his implementation uses six first order all-pass filters in series, with delay time modulated by a sinusoidal. This implementation was created to be clear, not efficient. Obvious modifications include using a table lookup for the lfo, not updating the filter delay times every sample, and not tuning all of the filters to the same delay time. It sounds sensationally good! } interface uses Kol, AudioUtils, SimpleAllpass; type PPhaser = ^TPhaser; TPhaser = object(Tobj) private FSamplerate: single; FFeedback: single; FlfoPhase: single; FDepth: single; FOldOutput: single; FMinDelta: single; FMaxDelta: single; FLfoStep: single; FAllpDelays: array[0..5] of PAllpassdelay; FLowFrequency: single; FHighFrequency: single; procedure SetRate(TheRate: single); // cps procedure SetFeedback(TheFeedback: single); // 0 -> <1. procedure SetDepth(TheDepth: single); procedure SetHighFrequency(const Value: single); procedure SetLowFrequency(const Value: single); // 0 -> 1. procedure SetRange(LowFreq, HighFreq: single); // Hz public destructor Destroy; virtual; function Process(inSamp: single): single; property Rate: single write setrate;//In Cycles per second property Depth: single read Fdepth write setdepth;//0.. 1 property Feedback: single read FFeedback write setfeedback; //0..< 1 property Samplerate: single read Fsamplerate write Fsamplerate; property LowFrequency: single read FLowFrequency write SetLowFrequency; property HighFrequency: single read FHighFrequency write SetHighFrequency; end; function NewPhaser: PPhaser; implementation { TPhaser } function NewPhaser: PPhaser; var i: integer; begin New(Result, Create); with Result^ do begin Fsamplerate := 44100; FFeedback := 0.7; FlfoPhase := 0; Fdepth := 1; FOldOutput := 0; setrange(440,1720); setrate(0.5); for i := 0 to 5 do FAllpDelays[i] := NewAllpassDelay; end; end; destructor TPhaser.Destroy; var i: integer; begin for i := 5 downto 0 do FAllpDelays[i].Free; inherited; end; procedure TPhaser.SetDepth(TheDepth: single); // 0 -> 1. begin Fdepth := TheDepth; end; procedure TPhaser.SetFeedback(TheFeedback: single);//0..1; begin FFeedback := TheFeedback; end; procedure TPhaser.SetRange(LowFreq, HighFreq: single); begin FMinDelta := LowFreq / (FsampleRate / 2); FMaxDelta := HighFreq / (FsampleRate / 2); end; procedure TPhaser.SetRate(TheRate: single); begin FLfoStep := 2 * _PI * (Therate / FsampleRate); end; const _1:single=1; _2:single=2; function TPhaser.Process(inSamp: single): single; var Delaytime, Output: single; i: integer; begin //calculate and Process phaser sweep lfo... Delaytime := FMinDelta + (FMaxDelta - FMinDelta) * ((sin(FlfoPhase) + 1) / 2); FlfoPhase := FlfoPhase + FLfoStep; if (FlfoPhase >= _PI * 2) then FlfoPhase := FlfoPhase - _PI * 2; //Process filter coeffs for i := 0 to 5 do FAllpDelays[i].setdelay(Delaytime); //calculate output Output := FAllpDelays[0].Process(FAllpDelays[1].Process (FAllpDelays[2].Process(FAllpDelays[3].Process(FAllpDelays[4].Process (FAllpDelays[5].Process(inSamp + FOldOutput * FFeedback)))))); FOldOutput := Output; Result := kDenorm + inSamp + Output * Fdepth; end; procedure TPhaser.SetHighFrequency(const Value: single); begin FHighFrequency := Value; setrange(FlowFrequency, FHighFrequency); end; procedure TPhaser.SetLowFrequency(const Value: single); begin FLowFrequency := Value; setrange(FlowFrequency, FHighFrequency); end; end.

from : thaddy[AT]thaddy[DOT]com
comment : And here the allpass: unit SimpleAllpass; { Unit: SimpleAllpass purpose: Simple allpass delay for creating reverbs and phasing/flanging Author: Copyright: Remarks: } interface uses kol, audioutils; type PAllpassDelay = ^TAllpassDelay; TAllpassdelay = object(Tobj) protected Fa1, Fzm1: single; public procedure SetDelay(delay: single);//sample delay time function Process(inSamp: single): single; end; function NewAllpassDelay: PAllpassDelay; implementation function NewAllpassDelay: PAllpassDelay; begin New(Result, Create); with Result^ do begin Fa1 := 0; Fzm1 := 0; end; end; function TallpassDelay.Process(Insamp: single): single; begin Result := kDenorm+inSamp * -Fa1 + Fzm1; Fzm1 := Result * Fa1 + inSamp + kDenorm; end; procedure TAllpassDelay.setdelay(delay: single);// In sample time begin Fa1 := (1 - delay) / (1 + delay); end; end.

from : Christian[AT]savioursofsoul[DOT]de
comment : You'll get a good performance boost by combining the 6 allpasses to one and rewriting that one to FPU code. Heavy speed increase AND you can make the number of allpasses variable as well. This would look similar to this: function TMasterAllpass.Process(const x:single):single; var a : array[0..1] of Single; b : Single; i : Integer; begin a[0]:=x*fA1+fY[0]; b:=a[0]*fA1; fY[0]:=b-x; i:=0; while i<fStages do begin a[1]:=b-fY[i+1]; b:=a[1]*fA1; fY[i+1]:=a[0]-b; a[0]:=b-fY[i+2]; b:=a[0]*fA1; fY[i+2]:=a[1]-b; Inc(i,2); end; a[1]:=b-fY[5]; b:=a[1]*fA1; fY[5]:=a[0]-b; Result:=a[1]; end; Now all you have to do is crawling into the FPU registers...

from : thaddy[AT]thaddy[DOT]com
comment : Point taken ;) Maybe we should combine all the stuff ;) Btw: It's lots of fun working from each others code, don't you think?

from : xcmotdf4[AT]aim[DOT]com
comment : YVD n 4Aw KF18 Z 8 http://www.xrumer.biz


Linked files
/* 
Date: Mon, 24 Aug 1998 07:02:40 -0700
Reply-To: music-dsp
Originator: music-dsp@shoko.calarts.edu
Sender: music-dsp
Precedence: bulk
From: "Ross Bencina" <rbencina@hotmail.com>
To: Multiple recipients of list <music-dsp>
Subject: Re: Phaser revisited [code included]
X-Comment:  Music Hackers Unite! http://shoko.calarts.edu/~glmrboy/musicdsp/music-dsp.html 
Status: RO

Hi again,

Thanks to Chris Towsend and Marc Lindahl for their helpful 
contributions. I now have a working phaser and it sounds great! It seems 
my main error was using a 'sub-sampled' all-pass reverberator instead of 
a single sample all-pass filter [what was I thinking? :)].

I have included a working prototype (C++) below for anyone who is 
interested. My only remaining doubt is whether the conversion from 
frequency to delay time [ _dmin = fMin / (SR/2.f); ] makes any sense 
what-so-ever.

Ross B.
*/
/*
    class: Phaser
    implemented by: Ross Bencina <rossb@kagi.com>
    date: 24/8/98

    Phaser is a six stage phase shifter, intended to reproduce the
    sound of a traditional analogue phaser effect.
    This implementation uses six first order all-pass filters in
    series, with delay time modulated by a sinusoidal.

    This implementation was created to be clear, not efficient.
    Obvious modifications include using a table lookup for the lfo,
    not updating the filter delay times every sample, and not
    tuning all of the filters to the same delay time.

    Thanks to:
    The nice folks on the music-dsp mailing list, including...
    Chris Towsend and Marc Lindahl
    
    ...and Scott Lehman's Phase Shifting page at harmony central:
    http://www.harmony-central.com/Effects/Articles/Phase_Shifting/

*/

#define SR (44100.f)  //sample rate
#define F_PI (3.14159f)

class Phaser{
public:
	Phaser()  //initialise to some usefull defaults...
    	: _fb( .7f )
    	, _lfoPhase( 0.f )
    	, _depth( 1.f )
        , _zm1( 0.f )
    {
    	Range( 440.f, 1600.f );
    	Rate( .5f );
    }

    void Range( float fMin, float fMax ){ // Hz
    	_dmin = fMin / (SR/2.f);
        _dmax = fMax / (SR/2.f);
    }

    void Rate( float rate ){ // cps
    	_lfoInc = 2.f * F_PI * (rate / SR);
    }

    void Feedback( float fb ){ // 0 -> <1.
    	_fb = fb;
    }

    void Depth( float depth ){  // 0 -> 1.
     	_depth = depth;
    }

    float Update( float inSamp ){
    	//calculate and update phaser sweep lfo...
        float d  = _dmin + (_dmax-_dmin) * ((sin( _lfoPhase ) + 
1.f)/2.f);
        _lfoPhase += _lfoInc;
        if( _lfoPhase >= F_PI * 2.f )
        	_lfoPhase -= F_PI * 2.f;

        //update filter coeffs
        for( int i=0; i<6; i++ )
        	_alps[i].Delay( d );

        //calculate output
        float y = 	_alps[0].Update(
        			 _alps[1].Update(
                      _alps[2].Update(
                       _alps[3].Update(
                        _alps[4].Update(
                         _alps[5].Update( inSamp + _zm1 * _fb ))))));
        _zm1 = y;

        return inSamp + y * _depth;
    }
private:
	class AllpassDelay{
    public:
    	AllpassDelay()
        	: _a1( 0.f )
            , _zm1( 0.f )
            {}

        void Delay( float delay ){ //sample delay time
        	_a1 = (1.f - delay) / (1.f + delay);
        }

        float Update( float inSamp ){
        	float y = inSamp * -_a1 + _zm1;
        	_zm1 = y * _a1 + inSamp;

            return y;
        }
    private:
    	float _a1, _zm1;
    };

    AllpassDelay _alps[6];

    float _dmin, _dmax; //range
    float _fb; //feedback
    float _lfoPhase;
    float _lfoInc;
    float _depth;

    float _zm1;
};