Limited float precision and infinitely harmonic signal generation problem

Viewed 164

Suppose we need to generate a very long harmonic signal, ideally infinitely long. At first glance, the solution seems trivial:

Sample1:

   float t = 0;
   while (runned)
   {
      float v = sinf(w * t);
      t += dt;
   }

Unfortunately, this is a non-working solution. For t >> dt due to limited float precision incorrect values will be obtained. Fortunately we can call to mind that sin(2*PI* n + x) = sin(x) where n - arbitrary integer value, therefore modifying the example is not difficult to get an "infinite" analog

Sample2:

   float t = 0;
   float tau = 2 * M_PI / w;
   while (runned)
   {
      float v = sinf(w * t);
      t += dt;
      if (t > tau) t -= tau;
   }

For one physical simulation, I needed to get an infinite signal, which is the sum of harmonic signals, like that:

Sample3:

   float getSignal(float x)
   {
      float ret = 0;
      for (int i = 0; i < modNum; i++)
         ret += sin(w[i] * x);
      return ret;
   }

   float t = 0;
   while (runned)
   {
      float v = getSignal(t);
      t += dt;
   }

In this form, the code does not work correctly for large t, for similar reasons for the Sample1. The question is - how to get an "infinite" implementation of the Sample3 algorithm? I assume that the solution should looks like an Sample2. A very important note - generally speaking, w[i] is arbitrary and not harmonics, that is, all frequencies are not multiples of some base frequency, so i can't find common tau. Using types with greater precission (double, long double) is not allowed.

Thanks for your advice!

1 Answers

You can choose an arbitrary tau and store the phase reminders for each mod when subtracting it from t (as @Damien suggested in the comments).

Also, representing the time as t = dt * it where it is an integer can improve numerical stability (i think).

Maybe something like this:

int ndt = 1000;       // accumulate phase every 1000 steps for example
float tau = dt * ndt;

std::vector<float> phases(modNum, 0.0f);

int it = 0;
float t = 0.0f;
while (runned)
{
   t = dt * it;

   float v = 0.0f;
   for (int i = 0; i < modNum; i++)
   {
       v += sinf(w[i] * t + phases[i]);
   }

   if (++it >= ndt)
   {
       it = 0;
       for (int i = 0; i < modNum; ++i)
       {
           phases[i] = fmod(w[i] * tau + phases[i], 2 * M_PI);
       }
   }
}
Related