The purpose is to generate a "noisy" value centered on zero ( ie with Mean= 0),
which is more or less related to the previous step output value ( ie more or less correlated ),
and to allow to set the standard deviation of the output when a statistically stationary state is reached.
Added to an ideal value, this can simulate errors/perturbations.
The equations of numerical generators are general. Their parameters are further defined so that they are also a simulation of their continuous counterpart, in order to make them independent of the Timestep( delay between to updates); the code is written that way.
By definition, the covariance is the square of the standard deviation, ie Cov= StdDev2.
For random processes as defined below, the covariance of the ouput can be computed: it is worth noticing that
its equations are deterministic and can be computed separately from the perturbation equation.
This allow for a selection of an appropriate perturbation generator based on
its statistical properties which are deterministic, where the standard deviation can be viewed as
a measurement of the generated output inaccuracy.
"w01" denotes gaussian white noise, with mean= 0 and standard deviation= 1,
as provided by the function RVgauss01() below.
"w0S" denotes gaussian white noise, with mean= 0 and standard deviation= StdDev_w0S,
which can be modelled as wOS= StdDev_w0S* w01.
NF1Noise
float g_output, StdDev_WOS, StdDevInit;
//at init time
void initcoloredNoise1()
{
//_ compute K= exp(-k*T)...see first order linear filter
StdDev_WOS= K_StdDevOut* sqrt(1- K*K);
//_ to get constant statistical characteristics right from the start
StdDevInit= K_StdDevOut;
//_
g_output= StdDevInit*RVgauss01();
}
//------------
float coloredNoise1()
{
return g_output= K* g_output+ StdDev_WOS* RVgauss01();
}
Using Filter N1LL ( where A= 1-K) with input= StdDev_w0S/A*W01 is another equivalent implementation.
For high values of K, the output as defined hereabove slowly drifts but its covariance is bounded
, which somehow bounds the drift.
To get an unbounded drift, one should use a "random walk", ie
Xn= Xn-1+ T* StdDev_WOS* W01.
X0 and StdDev_X0 are given.
The output covariance is:
Cov_Xn= Cov_Xn-1+ T2* Cov_WOS.
For a constant Cov_WOS, this can be solved as:
Cov_Xn= Cov_X0+ n* T2* Cov_WOS.
The continuous formulation is:
dx/dt= b* wo1, hence
dCov_x/dt= b2.
Cov_WOS must then be equal to b2/T, and Cov_X varies then linearly with time as:
Cov_Xn= Cov_X0+ n* T* b2.
See Parameters computation for K1, K0 and A computations.
NF2Noise
//----------------
const float K_StdDevOut=...;
float g_Output2, g_Output, StdDev_WOS, StdDevInit;
//at init time ( I am not 100% sure of the theoretical soundness of g_Output initialization)
void initcoloredNoise2()
{
//to get constant statistical characteristics right from the start
StdDevInit= K_StdDevOut;
//
StdDev_WOS= K_StdDevOut* sqrt(A*(1- K0)*(1+K0-K1)/(1+ K0));
g_Output2= StdDevInit* RVgauss01();
g_Output= StdDevInit* RVgauss01();
float corr= -K1/(1+K0);
g_Output= corr*g_Output2+sqrt(1-corr*corr)*g_Output;
}
//--------------------
float coloredNoise2()
{
float TmpPrevOutput= g_Output;
g_Output= -K1* TmpPrevOutput- K0* g_Output2+ StdDev_WOS* RVgauss01();
g_Output2= TmpPrevOutput;
return g_Output;
}
/* RVgauss01() returns a gaussian purely random variable, with mean=0 and standard deviation= 1. Each call to RVgauss01() will return a value uncorrelated with any previous one.*/
double RVgauss01() ( void)
{
static int bSwitch_s= 1;
static double G2_s;
bSwitch^= 1;
if( bSwitch) return G2_s;
double v1, v2, w, y;
do
{
v1= 2.0* RVUniform01()- 1.0;
v2= 2.0* RVUniform01()- 1.0;
w= v1 * v1 + v2 * v2;
} while( w> 1);
y= sqrt( -2.0* log(w)/ w);
G2_s= y* v2;// is independent from y *v1
return y* v1;
}
RVapproxGauss01() is an "approximation" of RVgauss01(), which is probably good enough for use in games.The higher the N, the closer to a white noise probability density.
N=1 returns the uniform probability density on [-0.5, 0.5], N=2 returns a triangle probability density, N=3 a piece-wise parabolic probability density,...
N= 5 is a minimum to have a decent approximation, and N=12 allows code simplification.
Note: as opposed to RVgauss01(), fabs(RVapproxGauss01()) can never be greater than sqrt(3*N).
const int N=...;
const float K_nr= (float)sqrt(12.0f/ N);
float RVapproxGauss01()
{
float x= 0;
for( int i= 0; i< N; i++) x+= RVUniform01()-0.5f;
return x* K_nr;
}