C#为XNA动态音频包装Radian角度

时间:2010-12-29 11:34:39

标签: c# audio xna trigonometry sampling

编辑2 +回答

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

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

编辑1

这是我的WrapValue方法,应该考虑过以前发布。它没有克里斯泰勒那么复杂,但它对我的结果有同样的影响。

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 step {1}}是2 * 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触发函数的准确性有关,如何犯罪(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();
  }
    }
}

3 个答案:

答案 0 :(得分:1)

你没有展示你的环绕功能,我做了一个快速测试,以下没有发出任何声音点击。

我的快速换行功能

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);

答案 1 :(得分:1)

将double函数转换为浮点函数可以导致精度错误吗?无论如何,这个目的是什么,因为你最初使用双打并获得双后卫。毕竟,你每秒进行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;
}

答案 2 :(得分:1)

我猜你的Wrap功能正常,但是你没有正弦(x)你正在使用正弦(频率* x) - 如果频率* x没有产生2 * PI的圆整倍数然后你得到流行音乐。尝试包装Frequency * x以摆脱流行音乐。