0

編集 2 + 回答

0と の間をラップする必要があることがわかりましたFrequency*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 の間でラップしようとしたことが原因であると判断しました。

DynamicSoundEffectInstance を維持するだけの SineGenerator という小さなクラスを作成し、 で生成されたサンプル バッファをフィードしますMath.Sin()

私は正確に 44.1 または 48k のサンプリング レートを使用したいので、double x(フィードしている角度Math.Sin()) とdouble stepどこにstepあるかを保持してい2 * Math.PI / SAMPLING_FREQUENCYます。DynamicSoundEffectInstance.SubmitBuffer() のデータを生成するたびに、インクリメントxしてサンプル バッファーにstep追加します ( XNA は 16 ビットのサンプル深度しかサポートしていないため、a に切り捨てられます)。sin(frequency * x)short

角度が大きくなっても精度が失われないように、角度を 0 から 2*pi の間でラップしたほうがよいと思いますx。ただし、これを行うとクリックが発生します。double WrapValue(double val, double min, double max)MathHelper.WrapAngle() がおかしい場合に備えて、独自のメソッドを作成しました。Math.PI と -Math.PI の間のラップも、0 と 2*Math.PI の間のラップもクリックを取り除きません。ただし、わざわざ値をラップせずにそのまま大きくすると、クリックが消えます。

私はそれが .NET 三角関数の精度、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();
  }
    }
}
4

3 に答える 3

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);
于 2010-12-29T12:14:07.270 に答える
1

ラップ関数で double を float に変換すると、精度エラーが発生する可能性がありますか? とにかく、もともとダブルスを使用してダブルバックを取得しているため、これの目的は何ですか。結局、毎秒 48,000 の変換を行っているため、エラーが蓄積されます。xstep が 2PI を超える場合もラップ関数は機能しませんが、それがどのように起こるかわかりません...

float としてのキャストを停止しても問題が解決しない場合は、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;
}
于 2010-12-30T20:41:11.190 に答える
1

あなたのラップ関数は正常に動作すると推測していますが、Sine(x) を使用していません。Sine(Frequency * x) を使用しています。Frequency*x が 2*PI の倍数を生成しない場合は、pop . ポップを取り除くために Frequency*x をラップしてみてください。

于 2011-01-03T17:07:38.930 に答える