绘制离散时间信号表示幅度调制 [英] Plotting a discrete-time signal shows amplitude modulation

查看:263
本文介绍了绘制离散时间信号表示幅度调制的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用canvas元素渲染简单的离散时间信号.但是,表示似乎不准确.正如您在代码段中看到的那样,在频率达到某个阈值之后,信号出现进行了幅度调制.即使它远低于<50Hz的奈奎斯特极限(在此示例中,假定采样率为100Hz). 对于非常低的频率,例如5Hz,它看起来非常好.

I'm trying to render a simple discrete-time signal using a canvas element. However, the representation seems to be inaccurate. As you can see in the code snippet the signal appears to be amplitude modulated after the frequency reaches a certain threshold. Even though it's well below the Nyquist limit of <50Hz (assuming a sampling rate of 100Hz in this example). For very low frequencies like 5Hz it looks perfectly fine.

我将如何正确呈现此图像?并适用于更复杂的信号(例如,歌曲的波形)吗?

How would I go about rendering this properly? And does it work for more complex signals (say, the waveform of a song)?

window.addEventListener('load', () => {
  const canvas = document.querySelector('canvas');
  const frequencyElem = document.querySelector('#frequency');
  const ctx = canvas.getContext('2d');

  const renderFn = t => {
    const signal = new Array(100);
    const sineOfT = Math.sin(t / 1000 / 8 * Math.PI * 2) * 0.5 + 0.5;
    const frequency = sineOfT * 20 + 3;

    for (let i = 0; i < signal.length; i++) {
      signal[i] = Math.sin(i / signal.length * Math.PI * 2 * frequency);
    }

    frequencyElem.innerText = `${frequency.toFixed(3)}Hz`

    render(ctx, signal);
    requestAnimationFrame(renderFn);
  };

  requestAnimationFrame(renderFn);
});

function render(ctx, signal) {
  const w = ctx.canvas.width;
  const h = ctx.canvas.height;

  ctx.clearRect(0, 0, w, h);

  ctx.strokeStyle = 'red';
  ctx.beginPath();

  signal.forEach((value, i) => {
    const x = i / (signal.length - 1) * w;
    const y = h - (value + 1) / 2 * h;

    if (i === 0) {
      ctx.moveTo(x, y);
    } else {
      ctx.lineTo(x, y);
    }
  });

  ctx.stroke();
}

@media (prefers-color-scheme: dark) {
  body {
    background-color: #333;
    color: #f6f6f6;
  }
}

<canvas></canvas>
<br/>
Frequency: <span id="frequency"></span>

推荐答案

在我看来不错.在较高频率下,当峰落在两个样本之间时,采样点可能比峰低很多.

It looks right to me. At higher frequencies, when the peak falls between two samples, the sampled points can be a lot lower than the peak.

如果信号仅具有<奈奎斯特,然后可以从其样本中重建信号.这并不意味着样本看起来像信号.

If the signal only has frequencies < Nyquist, then the signal can be reconstructed from its samples. That doesn't mean that the samples look like the signal.

只要您的信号被2倍或更多(或大约2倍)过采样,就可以通过在采样点之间使用三次插值来相当精确地绘制信号.例如,请参见此处的Catmull-Rom插值: https://en.wikipedia.org/wiki/Cubic_Hermite_spline

As long as your signal is oversampled by 2x or more (or so), you can draw it pretty accurately by using cubic interpolation between the sample points. See, for example, Catmull-Rom interpolation in here: https://en.wikipedia.org/wiki/Cubic_Hermite_spline

您可以在HTML Canvas中使用bezierCurveTo方法绘制这些插值曲线.如果需要使用线,则应该找到样本之间出现的任何最大或最小点,并将它们包括在路径中.

You can use the bezierCurveTo method in HTML Canvas to draw these interpolated curves. If you need to use lines, then you should find any maximum or minimum points that occur between samples and include those in your path.

我已经编辑了您的代码段,以将bezierCurveTo方法与下面的Catmull-Rom插值一起使用:

I've edited your snippet to use the bezierCurveTo method with Catmull-Rom interpolation below:

window.addEventListener('load', () => {
  const canvas = document.querySelector('canvas');
  const frequencyElem = document.querySelector('#frequency');
  const ctx = canvas.getContext('2d');

  const renderFn = t => {
    const signal = new Array(100);
    const sineOfT = Math.sin(t / 1000 / 8 * Math.PI * 2) * 0.5 + 0.5;
    const frequency = sineOfT * 20 + 3;

    for (let i = 0; i < signal.length; i++) {
      signal[i] = Math.sin(i / signal.length * Math.PI * 2 * frequency);
    }

    frequencyElem.innerText = `${frequency.toFixed(3)}Hz`

    render(ctx, signal);
    requestAnimationFrame(renderFn);
  };

  requestAnimationFrame(renderFn);
});

function render(ctx, signal) {
  const w = ctx.canvas.width;
  const h = ctx.canvas.height;

  ctx.clearRect(0, 0, w, h);

  ctx.strokeStyle = 'red';
  ctx.beginPath();

  const dx = w/(signal.length - 1);
  const dy = -(h-2)/2.0;
  const c = 1.0/2.0;

  for (let i=0; i < signal.length-1; ++i) {
    const x0 = i * dx;
    const y0 = h*0.5 + signal[i]*dy;
    const x3 = x0 + dx;
    const y3 = h*0.5 + signal[i+1]*dy;
    let x1,y1,x2,y2;
    if (i>0) {
      x1 = x0 + dx*c;
      y1 = y0 + (signal[i+1] - signal[i-1])*dy*c/2;
    } else {
      x1 = x0;
      y1 = y0;
      ctx.moveTo(x0, y0);
    }
    if (i < signal.length-2) {
      x2 = x3 - dx*c;
      y2 = y3 - (signal[i+2] - signal[i])*dy*c/2;
    } else {
      x2 = x3;
      y2 = y3;
    }
    ctx.bezierCurveTo(x1,y1,x2,y2,x3,y3);
  }

  ctx.stroke();
}

@media (prefers-color-scheme: dark) {
  body {
    background-color: #333;
    color: #f6f6f6;
  }
}

<canvas></canvas>
<br/>
Frequency: <span id="frequency"></span>

这篇关于绘制离散时间信号表示幅度调制的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆