編集 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();
}
}
}