简体   繁体   English

失去双精度C#

[英]Loss of double precision C#

I am experiencing precision loss when im using doubles and i cant seem to find where the precision is lost. 我在使用double时遇到精度损失,我似乎找不到丢失精度的地方。 I am writing a software synthesizer and for some reason the input frequency for an oscillator(sound-wave generator) gets heavily aliased somewhere along the way. 我正在写一个软件合成器,由于某种原因,振荡器(声波发生器)的输入频率会在此过程中的某个地方严重混叠。 The oscillator is supposed to generate a triangle waveform and the correct frequency is passed to the method but when the result is played in the speakers i can clearly hear that the sound snaps to different frequencies. 振荡器应该产生一个三角波形,并且正确的频率被传递给该方法,但是当结果在扬声器中播放时,我可以清楚地听到声音突然跳到不同的频率。

I am using NAudio (http://naudio.codeplex.com/) and this method is run once for every sample i want to generate. 我正在使用NAudio(http://naudio.codeplex.com/),并且我要生成的每个样本都会运行一次此方法。

Here is the code: 这是代码:

double counter = 0;
int samplecounter = 0;

public double GetNextTriangle(double frequency)
{
    double samplesperwave = (double)Parent.WaveFormat.SampleRate / frequency;
    double length = (double)samplecounter / samplesperwave;
    if (length.CompareTo(0.25) == -1)
    {
        counter = length * 4.0;
    }
    else if (length.CompareTo(0.75) == -1)
    {
        counter = 1.0 - (length - 0.25) * 4.0;
    }
    else
    {
        counter = -1.0 + (length - 0.75) * 4.0;
    }
    samplecounter++;
    samplecounter = samplecounter > samplesperwave ? 0 : samplecounter;
    return counter;
}

Thanks in advance! 提前致谢! //Mats //垫子

Your problem is not one of precision. 您的问题不是精度之一。 The issue is that your function does not define a triangle wave. 问题是您的函数没有定义三角波。 Each time samplecounter is reset to 0, the next returned value from the function is 0. Next time round length is set to 0, the first if branch is executed and counter is set to 0. 每次将samplecounter重置为0时,该函数的下一个返回值samplecounter 0。将下一个舍入长度设置为0,则执行第一个if分支,并将counter设置为0。

When faced with a problem like this you should plot the output. 遇到这样的问题时,应绘制输出。 Had you done so you would have seen immediately that your function does not produce a triangle wave form. 如果这样做,您将立即发现您的函数不会产生三角波形。 Here's some code that does: 这是一些执行的代码:

static double GetNextTriangle(double frequency)
{
    double samplesperwave = Parent.WaveFormat.SampleRate / frequency;
    double t = samplecounter / samplesperwave;
    counter = 1.0 - 4.0 * Math.Abs(Math.Round(t - 0.25) - (t - 0.25));
    samplecounter++;
    return counter;
}

Again, I cannot stress enough that you must plot and visualize the output from your code to gain insight into its behaviour. 再次强调,您必须对代码的输出进行绘图和可视化以深入了解其行为。

To try and get a better idea of what's wrong, let's visualize the data. 为了尝试更好地了解问题所在,我们将数据可视化。 Here's two cycles of a 2 Hz wave at 41 samples per second: 这是2 Hz的波形的两个周期,每秒41个采样:

在此处输入图片说明

There's a blip around sample 20. It looks like the samples aren't fitting a triangle wave properly. 样本20周围出现了一个点。样本似乎未正确拟合三角波。

Let's go over the math of a triangle wave. 让我们回顾一下三角波的数学原理。 What you're doing is sampling a continuous function at discrete points. 您正在做的是在离散点采样连续函数。 First, define a continuous triangle wave of frequency f (or 1/T ) in hertz: 首先,以赫兹定义一个频率为f (或1/T )的连续三角波:

tri(t) =   {  4*f*t,            0     <= t < T/4   }
       =   { -4*f*(t - T/2),    T/4   <= t < 3*T/4 }
       =   {  4*f*(t - T),      3*T/4 <= t < T     }
       =   tri(t - T)  [it's periodic!]

You now want to sample this continuous function. 现在,您要对该连续函数进行采样。 So you define a sampling rate, s (or 1/U ) in samples per second. 因此,您可以定义每秒采样中的采样率s (或1/U )。 Now, the n th sample will simply be tri(n*U) : 现在,第n个样本将只是tri(n*U)

tri[n] = tri(n*U)
       =   {  4*f*n*U,            0     <= n*U < T/4   }
       =   { -4*f*(n*U - T/2),    T/4   <= n*U < 3*T/4 }
       =   {  4*f*(n*U - T),      3*T/4 <= n*U < T     }

Let's clean it up a bit by defining the normalized period, P = T/U , and the normalized frequency, F = f/s = U/T : 让我们通过定义归一化周期P = T/U和归一化频率F = f/s = U/T对其进行清理:

tri[n] =   {  4*F*n,            0     <= n < P/4   }
       =   { -4*F*(n - P/2),    P/4   <= n < 3*P/4 }
       =   {  4*F*(n - P),      3*P/4 <= n < P     }

Now we get this: 现在我们得到:

在此处输入图片说明

Don't worry about the apparent "blips" at the tips; 不必担心提示中明显的“斑点”; they are to be expected and you shouldn't try to avoid them. 他们是意料之中的,您不应该避免它们。

Here's the code: 这是代码:

public static double GetNextTriangle(int sample, double frequency, double sampleRate)
{
    double T = 1d / frequency;
    double U = 1d / sampleRate;

    double P = T / U;
    double F = U / T;

    double n = (double)sample;
    n %= P; // restrict n to the domain [0, P)

    if ((n >= 0) && (n < (P / 4d)))
    {
        return 4d * F * n;
    }
    else if ((n >= (P / 4d)) && (n < (3d * P / 4d)))
    {
        return -4d * F * (n - (P / 2d));
    }
    else // if ((n >= (3d * P / 4d)) && (n < P))
    {
        return 4d * F * (n - P);
    }
}

The floating point operations here are well conditionned. 此处的浮点运算条件良好。 But you may see an alisaing problem related to sampling if 1/frequency is not a multiple of 1/sampleRate. 但是,如果1 / frequency不是1 / sampleRate的倍数,则可能会遇到与采样有关的别名问题。

Try this matlab code if you can 如果可以,请尝试使用此matlab代码

sampleRate=5000;
frequency=700;
sampleperwave=sampleRate/frequency;
samplecounter=0:floor(sampleperwave);
samplecounter=repmat(samplecounter,1,5);
length=samplecounter/sampleperwave;
wave=-1+4*(length-0.75);
wave(length<0.75)=1-4*(length(length<0.75)-0.25);
wave(length<0.25)=4*length(length<0.25);
figure; stem(wave); hold on; plot(wave,'r')

不连续性

You could try to declare samplecounter as double and increment it with a modulo 您可以尝试将samplecounter声明为double并以模为单位递增

samplecounter++;
samplecounter = samplecounter % samplesperwave;

Or back in matlab 或回到Matlab

samplecounter=0:length(samplecounter)-1;
samplecounter=rem(samplecounter,sampleperwave);
len=samplecounter/sampleperwave;
wave=-1+4*(len-0.75);
wave(len<0.75)=1-4*(len(len<0.75)-0.25);
wave(len<0.25)=4*len(len<0.25);
figure; stem(wave); hold on; plot(wave,'r')

混叠

The whole approach seems wrong. 整个方法似乎是错误的。 I'd go with something like this: 我会选择这样的东西:

public class RectWave
{ // triangular wave in range [0,1] with given frequency
    public double GetNextSample()
    {
        if (_ptr == _wave.Length)
            _ptr = 0;
        return _wave[_ptr++];
    }

    public RectWave(int sampleRate = 441000, int period = 20)
    { 
        _sampleRate = sampleRate;
        _period = period;
        _wave = new double[(int)(_sampleRate * _period / 1000.0)];  // one cycle
        _ptr = 0;
        BuildWave();
    }

    private void BuildWave()
    {
        double dt = 1000.0 / _sampleRate;   // in msec
        double slope = 1.0 / (_period / 2.0);   // 1.0 => peek 
        for (int i=0; i <= _wave.Length/2; ++i)
        {
            _wave[i] = _wave[_wave.Length - i - 1] = i * dt * slope; 
        }
    }

    private int _sampleRate;    // in Hz
    private int _period;        // in msec
    private double[] _wave;     // the data
    private int _ptr;           // current sample
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM