3

iPhone用の小さなソフトウェアシンセを書きました。
パフォーマンスをさらに調整するために、Sharkを使用してアプリケーションを測定したところ、float/SInt16変換で多くの時間が失われていることがわかりました。
そこで、「すぐに使用できる」SInt16サンプルを返すルックアップテーブルを事前に計算して、変換を回避するためにいくつかの部分を書き直しました。これは今のところうまく機能します。
現在、整数演算のみを使用するようにいくつかのフィルターとADSRエンベロープの実装を書き直そうとしていますが、浮動小数点数なしで乗算/除算を実行する方法についていくつかのヒントを使用できます。
私はiPhoneの標準フォーマットをターゲットにしています:

  • LPCM
  • 16ビット整数サンプル

フロートを使用せずに最終サンプルに振幅を適用するための良いアプローチは何ですか?

編集:
私がこれまでに理解した唯一のことは、現在のサンプルを右シフトすることで2の累乗で割ることができるということです。

inBuffer[frame] = wavetable[i % cycleLengthInSamples] >> 4;

しかし、それを使ってスムーズなADSRエンベロープを作成するためのエレガントな方法は考えられません。

Edit2: すべての素晴らしい答えをありがとう!
私の現在のアプローチ:

  • すべてのADSRエンベロープ値を正のSInt16範囲にします
  • ウェーブテーブルからの現在の値を乗算します(中間体をSInt32として格納します)
  • 結果を右に16シフトします

これはうまくいくようです:)

4

4 に答える 4

4

この場合は16ビットを使用しているため、固定小数点は適切です。最も簡単な方法は、必要な精度に応じて10の累乗を掛けることです。中間体として32ビットintを使用できる場合は、まともな精度を得ることができるはずです。最後に、16ビット整数に変換して戻したり、必要に応じて丸めたり切り捨てたりすることができます。

編集:値を大きくするには、左にシフトします。シフトの結果をより正確なタイプに格納します(必要に応じて32ビットまたは64ビット)。符号付きタイプを使用している場合、単純なシフトは機能しません

2つの固定小数点数を乗算または除算する場合は注意してください。乗算は(a * n)*(b n )になり、abnの代わりにab n^2になります。除算は(a n)/(b n)であり、((a n)/ b)ではなく(a / b)です。そのため、10の累乗を使用することを提案しました。これにより、固定小数点に慣れていない場合でも、間違いを簡単に見つけることができます。

計算が完了したら、右にシフトして16ビット整数に戻ります。派手になりたい場合は、シフトする前に丸めを行うこともできます。

効率的な固定小数点の実装に本当に興味がある場合は、読んでおくことをお勧めします。http://www.digitalsignallabs.com/fp.pdf

于 2009-09-18T15:51:29.337 に答える
3

この SO の質問に対する回答は、実装に関してかなり包括的です。あちらで見たよりももう少し説明があります:

1 つのアプローチは、[-1.0,1.0 など] のように、すべての数値を範囲に強制することです。次に、それらの数値を範囲 [-2^15,(2^15)-1] にマップします。例えば、

Half = round(0.5*32768); //16384
Third = round((1.0/3.0)*32768); //10923

この2つの数を掛けると、

Temp = Half*Third; //178962432
Result = Temp/32768; //5461 = round(1.0/6.0)*32768

最後の行で 32768 で割ることは、追加のスケーリング手順が必要な乗算についてPatrosが指摘した点です。2^N スケーリングを明示的に記述すると、これはより理にかなっています。

x1 = x1Float*(2^15);
x2 = x2Float*(2^15);
Temp = x1Float*x2Float*(2^15)*(2^15);
Result = Temp/(2^15); //get back to 2^N scaling

それが算数です。実装では、2 つの 16 ビット整数の乗算には 32 ビットの結果が必要であるため、Temp は 32 ビットである必要があることに注意してください。また、32768 は 16 ビット変数では表現できないため、コンパイラは 32 ビットの即値を作成することに注意してください。そして、すでに指摘したように、2 の累乗で乗算/除算に移行できるので、次のように書くことができます。

N = 15;
SInt16 x1 = round(x1Float * (1 << N));
SInt16 x2 = round(x2Float * (1 << N));
SInt32 Temp = x1*x2;
Result = (SInt16)(Temp >> N);
FloatResult = ((double)Result)/(1 << N);

しかし、[-1,1) が適切な範囲ではないとしますか? 数値を [-4.0,4.0) などに制限したい場合は、N = 13 を使用できます。この場合、2 進小数点の前に 2 ビット、後ろに 13 ビットの 1 符号ビットがあります。これらは、それぞれ 1.15 および 3.13 固定小数点小数型と呼ばれます。ヘッドルームの分数で精度を交換します。

分数型の加算と減算は、飽和に気を付けている限り問題なく機能します。除算については、パトロスが言ったように、スケーリングは実際には相殺されます。だからあなたはしなければならない

Quotient = (x1/x2) << N;

または、精度を維持するために

Quotient = (SInt16)(((SInt32)x1 << N)/x2); //x1 << N needs wide storage

整数による乗算と除算は正常に機能します。たとえば、6 で割るには、次のように書くだけです。

Quotient = x1/6; //equivalent to x1Float*(2^15)/6, stays scaled

そして、2の累乗で割った場合、

Quotient = x1 >> 3; //divides by 8, can't do x1 << -3 as Patros pointed out

ただし、整数の足し算と引き算は単純には機能しません。まず、整数が xy 型に適合するかどうかを確認し、同等の小数型を作成して続行する必要があります。

これがアイデアに役立つことを願っています。クリーンな実装については、他の質問のコードを見てください。

于 2009-09-19T06:59:26.040 に答える
1

高速乗算アルゴリズムについて説明しているこのページをご覧ください。

http://www.newton.dep.anl.gov/askasci/math99/math99199.htm

于 2009-09-18T16:02:19.137 に答える
1

一般に、符号付き 16.16 固定小数点表現を使用するとします。そのため、32 ビット整数には符号付き 16 ビット整数部分と 16 ビット小数部分があります。次に、iPhone の開発でどの言語が使用されているかはわかりませんが (Objective-C でしょうか?)、この例は C で書かれています。

#include <stdint.h>

typedef fixed16q16_t int32_t ;
#define FIXED16Q16_SCALE 1 << 16 ;

fixed16q16_t mult16q16( fixed16q16_t a, fixed16q16_t b )
{
    return (a * b) / FIXED16Q16_SCALE ;
}

fixed16q16_t div16q16( fixed16q16_t a, fixed16q16_t b )
{
    return (a * FIXED16Q16_SCALE) / b ;
}

上記は単純化された実装であり、算術オーバーフローからの保護を提供しないことに注意してください。たとえば div16q16() では、精度を維持するために除算の前に乗算を行っていますが、オペランドによっては演算がオーバーフローする可能性があります。これを克服するために、64 ビットの中間を使用できます。また、整数除算を使用するため、除算は常に切り捨てられます。これにより最高のパフォーマンスが得られますが、反復計算の精度に影響を与える可能性があります。修正は簡単ですが、オーバーヘッドが増えます。

定数の 2 の累乗で乗算または除算する場合、ほとんどのコンパイラは単純な最適化を検出し、シフトを使用することに注意してください。ただし、C は負の符号付き整数の右シフトの動作を定義していないため、安全性と移植性のためにコンパイラに任せました。使用している言語に関係なく、YMV を使用できます。

OO 言語では、fixed16q16_t は当然、演算子のオーバーロードを持つクラスの候補になるため、通常の算術型のように使用できます。

タイプ間の変換が役立つ場合があります。

double fixed16q16_to_double( fixed16q16_t fix )
{
    return (double)fix / FIXED16Q16_SCALE ;
}

int fixed16q16_to_int( fixed16q16_t fix )
{
    // Note this rounds to nearest rather than truncates
    return ((fix + FIXED16Q16_SCALE/2)) / FIXED16Q16_SCALE ;
}

fixed16q16_t int_to_fixed16q16( int i )
{
    return i * FIXED16Q16_SCALE ;
}

fixed16q16_t double_to_fixed16q16( double d )
{
    return (int)(d * FIXED16Q16_SCALE) ;
}

これが基本です。より洗練されたものにして、三角関数やその他の数学関数を追加することができます。

固定の加算と減算は、組み込みの + 演算子と - 演算子とそのバリアントで機能します。

于 2009-09-21T20:34:33.747 に答える