【问题标题】:C# Wrapping Radian Angles for XNA Dynamic AudioXNA 动态音频的 C# 环绕弧度角
【发布时间】:2010-12-29 11:34:39
【问题描述】:

编辑 2 + 答案

原来我需要在0Frequency*2*Math.pi 之间进行换行。

所有发帖的人都为解决这个问题做出了贡献。由于客人的声誉最低,我只是将他的帖子标记为答案。非常感谢大家,这让我发疯了!

编辑 1

这是我的 WrapValue 方法,之前应该考虑过发布。它不像 Chris Taylor 那样复杂,但它对我的影响是一样的。

public static double WrapValue(double value, double min, double max)
{
    if (value > max)
        return (value - max) + min;
    if (value < min)
        return max - (min - value);
    return value;
}

这可能适合 Gamedev,但它不是游戏性的,而是更多的代码和数学,所以我把它放在这里。

我正在尝试使用新的 XNA 4.0 DynamicSoundEffectInstance 类将我的 Xbox 变成一个数字乐器,并且我每秒都会收到一次点击。我已经确定这是由于任何尝试将我的域值包装在 0 和 2*pi 之间造成的。

我编写了一个名为 SineGenerator 的小类,它只维护一个 DynamicSoundEffectInstance 并将使用Math.Sin() 生成的样本缓冲区提供给它。

因为我想要精确并使用 44.1 或 48k 采样率,所以我保留了 double x(我输入的角度 Math.Sin())和 double step,其中 step2 * Math.PI / SAMPLING_FREQUENCY .每次我为 DynamicSoundEffectInstance.SubmitBuffer() 生成数据时,我都会将 x 增加 step 并将 sin(frequency * x) 添加到我的样本缓冲区(截断为 short,因为 XNA 仅支持 16 位样本深度)。

我认为我最好将角度包裹在 0 和 2*pi 之间,这样我就不会因为 x 变大而失去精度。但是,这样做会引入点击。我编写了自己的 double WrapValue(double val, double min, double max) 方法,以防 MathHelper.WrapAngle() 搞砸了。 Math.PI 和 -Math.PI 或 0 和 2*Math.PI 之间的环绕都不会消除点击。但是,如果我不费心包装该值并让它增长,那么点击就会消失。

我认为这与 .NET trig 函数的准确性有关,如何 sin(0) != sin(2*pi),但我知道的不够多,无法判断。

我的问题:为什么会发生这种情况,我是否应该费心折角?

代码:

using System;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework;

namespace TestDynAudio
{
    class SineGenerator
    {
  // Sample rate and sample depth variables
  private const int BUFFER_SAMPLE_CAPACITY = 1024;
  private const int BIT_RATE = 16;
  private const int SAMPLING_FREQUENCY = 48000;
  private readonly int BYTES_PER_SAMPLE;
  private readonly int SAMPLE_BUFFER_SIZE;

        private DynamicSoundEffectInstance dynSound = new DynamicSoundEffectInstance(SAMPLING_FREQUENCY, AudioChannels.Mono);
  private double x = 0; // The domain or angle value
  private double step;  // 48k / 2pi, increment x by this for each sample generated
  private byte[] sampleData; // The sample buffer
  private double volume = 1.0f; // Volume scale value

  // Property for volume
  public double Volume
  {
   get { return volume; }
   set { if (value <= 1.0 && value >= 0.0) volume = value; }
  }

  // Property for frequency
  public double Frequency { get; set; }

        public SineGenerator()
        {
   Frequency = 440; // Default pitch set to A above middle C
   step = Math.PI * 2 / SAMPLING_FREQUENCY;
   BYTES_PER_SAMPLE = BIT_RATE / 8;
   SAMPLE_BUFFER_SIZE = BUFFER_SAMPLE_CAPACITY * BYTES_PER_SAMPLE;
   sampleData = new byte[SAMPLE_BUFFER_SIZE];

   // Use the pull-method, DynamicSoundEffectInstance will
   // raise an event when more samples are needed
   dynSound.BufferNeeded += GenerateAudioData;
        }

  private void buildSampleData()
  {
   // Generate a sample with sin(frequency * domain),
   //   Convert the sample from a double to a short,
   //   Then write the bytes to the sample buffer
   for (int i = 0; i < BUFFER_SAMPLE_CAPACITY; i++)
   {
    BitConverter.GetBytes((short)((Math.Sin(Frequency * x) * (double)short.MaxValue) * volume)).CopyTo(sampleData, i * 2);

    // Simple value wrapper method that takes into account the
    // different between the min/max and the passed value
    x = MichaelMath.WrapValue(x + step, 0, 2f * (Single)Math.PI);
   }
  }

  // Delegate for DynamicSoundInstance, generates samples then submits them
  public void GenerateAudioData(Object sender, EventArgs args)
  {
   buildSampleData();
   dynSound.SubmitBuffer(sampleData);
  }

  // Preloads a 3 sample buffers then plays the DynamicSoundInstance
  public void play()
  {
   for (int i = 0; i < 3; i++)
   {
    buildSampleData();
    dynSound.SubmitBuffer(sampleData);
   }
   dynSound.Play();
  }

  public void stop()
  {
   dynSound.Stop();
  }
    }
}

【问题讨论】:

  • 你说如果你让它生长它就会消失;这是否意味着如果您从 0 开始并从那里出去,则根本没有点击,或者它在某个时候消失?您是否尝试过其他 2*pi 间隔,如 1*pi - 3*pi,或 2C*pi,如 100*pi 至 200*pi?
  • 我尝试了 2*pi 的倍数,但不幸的是它只会增加点击之间的时间。不过谢谢你的建议。

标签: c# audio xna trigonometry sampling


【解决方案1】:

我猜你的 Wrap 函数工作正常,但你没有使用 Sine(x) 你正在使用 Sine(Frequency * x) - 如果 Frequency*x 不产生 2*PI 的整数倍,那么你得到流行音乐。尝试包装 Frequency*x 以消除流行音乐。

【讨论】:

  • Wrapping Frequency * x 不起作用,但是在查看我在回答 Jason Goemaat 发布后制作的日志文件后,发现 wrap 函数的上限需要为 Frequency*2*pi .
【解决方案2】:

你没有显示你的换行功能,我做了一个快速测试,下面没有发出任何咔哒声。

我的快速换行功能

public static float Wrap(float value, float lower, float upper)
{
  float distance = upper - lower;
  float times = (float)System.Math.Floor((value - lower) / distance);

  return value - (times * distance);
}

这样称呼

x = Wrap((float)(x + step), 0, 2 * (float)Math.PI);

【讨论】:

  • 感谢您指出我遗漏了 wrap 方法,将其添加到顶部。使用 wrap 方法时根本没有点击?我用你的替换了我的,完全相同的呼叫,但我仍然每秒都能获得点击。
  • @bearcdp,您是在 XBOX 还是 PC 上遇到这种情况?我没有 XBOX,只能在 PC 上玩 XNA,所以这可能是原因。您的 wrap 功能看起来不错,只是为了确定我在 PC 上使用您的 wrap 功能进行了测试,它似乎也可以正常工作。我没有 Xbox 开发经验,但这可能与性能有关吗?您是否尝试过包装值但忽略结果的额外工作,只是让x 递增,也许问题是构建缓冲区的时间略有增加,表现为点击。
  • 感谢您的回复,感谢您抽出宝贵时间帮助我。我现在只是在PC上运行。有趣的是,允许x 递增消除了这个问题。通过将缓冲区大小扩展到 8192 个样本,我还讨论了缓冲区构建时间过长的可能性,但我仍然得到了点击。
【解决方案3】:

为您的 wrap 函数将双精度数转换为浮点数会导致精度错误吗?无论如何,这样做的目的是什么,因为您最初使用的是双打并获得双打。毕竟,您每秒进行 48k 次转换,并且会累积错误。如果 xstep 超过 2PI,您的 wrap 函数也将不起作用,但我不知道这会如何发生......

如果您停止转换为浮点数并且这不能解决您的问题,我建议您创建一个 x2 变量并设置一个断点以查看为什么值不同:

// declarations
private double x2 = 0;
private byte[] sampleData2 = new byte[BUFFER_SAMPLE_CAPACITY * BYTES_PER_SAMPLE];

// replace your for loop with this and set a breakpoint on
// the line with Console.WriteLine()
for (int i = 0; i < BUFFER_SAMPLE_CAPACITY; i++)
{
    BitConverter.GetBytes((short)((Math.Sin(Frequency * x) * (double)short.MaxValue) * volume)).CopyTo(sampleData, i * 2);
    BitConverter.GetBytes((short)((Math.Sin(Frequency * x2) * (double)short.MaxValue) * volume)).CopyTo(sampleData2, i * 2);

    if (sampleData[i * 2] != sampleData2[i * 2] || sampleData[i * 2 + 1] != sampleData2[i * 2 + 1]) {
        Console.WriteLine("DIFFERENT VALUES!");
    }

    // Simple value wrapper method that takes into account the
    // different between the min/max and the passed value
    x = MichaelMath.WrapValue(x + step, 0, 2f * (Single)Math.PI);
    x2 = x2 + step;
}

【讨论】:

  • 我没有遵循这个确切的代码,但我确实写了一个包含所有 x 值的日志,原始 sin(x) 和 sin(frequency * x),它显示在2*pi 到 0 的交叉,虽然对于原始罪的过渡是平滑的,但将其乘以频率会导致 0.8 的跳跃,哎哟。我之前应该考虑做一些简单的旧视觉检查,谢谢你的回答!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-04
  • 2013-04-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多