10

私は48kHzで正弦波をサンプリングしていますが、正弦波の周波数範囲は0〜20000 Hzで、約100Hzのステップで変化する可能性があります。ルックアップテーブルアプローチを使用しています。したがって、4096の異なるフェーズの正弦波に対して4096のサンプルを生成します。この背後にある一般的な考え方は、ステップサイズを増やし、周波数ごとに異なるステップサイズを使用することだと思います。だから私は次のことをします(擬似コード)。しかし、ステップサイズが正弦波のサンプルを生成したい周波数にどのように関係するのかわかりませんか?たとえば、周波数が15000 Hzの場合、トラバースする必要のあるステップサイズはどれくらいになりますか?私のサンプルサイズ(4096)はこれには小さすぎますか?

 // Pseudocode
 uint16_t audio_sample[4096] = {...};
 NSTEP = freq; //???How is the step size going to be related to the freq here

 for(int i = 0; i < 4096; i = i+NSTEP)
 {
     sine_f(i) = audio_sample[i];
 }

前もって感謝します。

4

5 に答える 5

16

あなたは正しい方向に進んでいます-最初に正弦波LUTを生成する必要があります:

const int Fs = 48000;       // sample rate (Hz)
const int LUT_SIZE = 1024;  // lookup table size

int16_t LUT[LUT_SIZE];      // our sine wave LUT

for (int i = 0; i < LUT_SIZE; ++i)
{
    LUT[i] = (int16_t)roundf(SHRT_MAX * sinf(2.0f * M_PI * (float)i / LUT_SIZE));
}                           // fill LUT with 16 bit sine wave sample values

このLUTを生成する必要があるのは、たとえば初期化中に1回だけであることに注意してください。

正弦波LUTができたので、これを使用して、位相アキュムレータを使用して任意の周波数を生成できます。

const int BUFF_SIZE = 4096;  // size of output buffer (samples)
int16_t buff[BUFF_SIZE];     // output buffer

const int f = 1000;          // frequency we want to generate (Hz)

const float delta_phi = (float) f / Fs * LUT_SIZE;
                             // phase increment

float phase = 0.0f;          // phase accumulator

// generate buffer of output
for (int i = 0; i < BUFF_SIZE; ++i)
{
    int phase_i = (int)phase;        // get integer part of our phase
    buff[i] = LUT[phase_i];          // get sample value from LUT
    phase += delta_phi;              // increment phase
    if (phase >= (float)LUT_SIZE)    // handle wraparound
        phase -= (float)LUT_SIZE;
}

注:より高品質の出力を得るには、phase_iとのLUT値の間で線形補間を使用できますphase_i + 1が、ほとんどのオーディオアプリケーションでは上記のアプローチで十分です。

于 2012-11-20T05:51:17.577 に答える
5

ルックアップテーブルアプローチでは、メモリを効率的に使用し、正弦波の第1象限のみを保存したい場合があります。

Then second quadrant = sin[180 - angle]  ; // for angles 90..180 degrees
third quadrant = -sin[angle-180];          // for 180..270
fourth quadrant = -sin[-angle+360]         // for 270..360

線形補間をお勧めしますが、正弦を生成するためのベクトル回転ベースのアプローチもあります(正弦と余弦の両方を同時に生成します)

x_0 = 1, y_0 = 0;
x_n+1 = x_n * cos(alpha) - y_n * sin(alpha)
y_n+1 = x_n * sin(alpha) + y_n * cos(alpha), 

ここで、alpha=周波数の位相差==2pi * fHz / Ts、fHzは生成される周波数(ヘルツ単位)、Tsはサンプリング時間(または1 / Ts =サンプリング周波数(例:44100 Hz))です。

これは、伝達関数f(z)が単位円(z = e ^ jomegaT)に2つの共役極を持つ共振フィードバックフィルターアプローチにつながります。

y[n] = y[n-1] - 2*cos(alpha) * y[n-2], with
y[0] = 0,
y[1] = sin(alpha)

楽しい部分は、その場でアルファ(cos(alpha))を変更できることです。このIIRフィルターアプローチの欠点は、定義上不安定であるということです。浮動小数点および特に固定小数点の不正確さは蓄積され、指数関数的減衰またはマグニチュードの指数関数的成長のいずれかにつながります。ただし、わずかな位相歪みを許容することで、これを修復できます。

反復ごとの増幅率が既知の CORDIC回転の場合のように、次のようになります。

K = sqrt(1+sin(alpha)^2);

x' = x - y*sin(alpha);
y' = y + x*sin(alpha);

one can do

x' = x - y * sin(alpha);
y'' = y + x' * sin(alpha);

これは、(x'、y'')に対して完全な円を生成しませんが、固定小数点演算を使用しても安定した楕円を生成します。(これは、アルファの値が比較的小さいことを前提としていることに注意してください。これは、周波数も比較的低いことを意味します。)

于 2012-11-20T06:50:17.890 に答える
3

高精度が必要な場合は、三角法のIDを使用して、小さなLUTとクリーンな正弦波の両方を使用できます。

static float gTrigSinHi[256], gTrigSinLo[256];
static float gTrigCosHi[256], gTrigCosLo[256];

////////////////////////////////////////
// Sets up the fast trig functions
void FastTrigInit()
{
    unsigned i;
    for(i = 0; i < 256; ++i)
    {
        gTrigSinHi[i] = sin(2.0 * M_PI / 0xFFFF * (i << 8));
        gTrigSinLo[i] = sin(2.0 * M_PI / 0xFFFF * (i << 0));
        gTrigCosHi[i] = cos(2.0 * M_PI / 0xFFFF * (i << 8));
        gTrigCosLo[i] = cos(2.0 * M_PI / 0xFFFF * (i << 0));
    }
}

////////////////////////////////////////
// Implements sin as
//      sin(u+v) = sin(u)*cos(v) + cos(u)*sin(v)
float FastSin(unsigned short val)
{
    unsigned char hi = (val >> 8);
    unsigned char lo = (val & 0xFF);
    return gTrigSinHi[hi] * gTrigCosLo[lo]
        + gTrigCosHi[hi] * gTrigSinLo[lo];
}

////////////////////////////////////////
// Implements cos as
//      cos(u+v) = cos(u)*cos(v) - sin(u)*sin(v)
float FastCos(unsigned short val)
{
    unsigned char hi = (val >> 8);
    unsigned char lo = (val & 0xFF);
    return gTrigCosHi[hi] * gTrigCosLo[lo]
        - gTrigSinHi[hi] * gTrigSinLo[lo];
}
于 2012-11-20T17:11:04.770 に答える
0

非常に良い答えです。これは古典的なソフトウェアDDSです。最近同じ問題に直面しています。フロートを使用する必要はありません

        UInt16 f = 400; // Hz, up to 16384 :)
        UInt16 delta = (UInt16)(((UInt32)f * LUT_SIZE ) / fmt_SamplesPerSec);
        UInt16 phase = 0;

        for (int i = 0; i < BUFF_SIZE; ++i)
        {
            buff[i] = LUT[phase];
            phase += delta;
            phase &= LUT_SIZE-1;
        }

フェーズラップアラウンドLUTサイズをマスクとします。そして、私の目的のために私はすでにこの要件のための巨大なMIPSを持っているので、象限を使用することを気にしないでください。

于 2013-07-24T21:10:28.380 に答える
-1

「http://en.wikipedia.org/wiki/X86_instruction_listings」によると、x80387以降を使用している場合は正弦波命令があるため、直接呼び出してください。プログラムにインラインアセンブリ言語を追加する方法を理解する必要があります。このようにして、入力値がテーブルの値と完全に一致していなくても心配する必要はありません。

于 2012-11-20T05:23:38.913 に答える