3

我爲iPhone編寫了一個小型軟件合成器。
爲了進一步調整性能,我使用Shark測量了我的應用程序,發現我在float/SInt16轉換中浪費了很多時間。
因此,我重寫了一些零件以通過預先計算返回「隨時可用」的SInt16樣本的查找表來避開轉換。迄今爲止,這工作得很好。
目前我正在嘗試重寫一些過濾器和我的ADSR包絡實現,只使用整數算術,但我可以使用一些tipps如何執行乘法/除法而不使用浮點數。
我靶向的iPhone canonical format避免浮點運算

  • LPCM
  • 16位整數樣品

什麼是振幅適用於我的最終樣本好的方法不使用浮動?

編輯:
我想通了,到目前爲止,我能以2的冪被右移我目前的樣本劃分的唯一的事情。

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

但我想不出任何優雅的方式來創建一個平滑的ADSR信封。

編輯2: 感謝您的所有偉大的答案!
我目前的做法:

  • 帶來所有我的ADSR包絡與來自波表的電流值(存儲的中間體如SINT32)
  • 移位的結果以16比值 到正SInt16範圍
  • 乘法正確的

這似乎工作:)

回答

4

固定點是好的,因爲在這種你使用16位的情況。最簡單的方法是根據您需要的精度乘以10的冪。如果你可以使用32位整數作爲中間值,你應該能夠獲得相當高的精度。最後你可以轉換回16位整數,舍入或截斷,如你所願。

編輯: 你想向左移動,使值變大。將移位結果存儲在更精確的類型中(32位或64位,取決於您需要的)。如果您使用的符號類型

簡單的移位就不會工作當心,如果你乘以或除以兩個固定的點數。乘以(a * n)*(b n),您將以 b n^2結束而不是 b n。 (a/b)而不是((a n)/ b)的分區是(a n)/(b n)。這就是爲什麼我建議使用10的權力,如果你不熟悉定點,很容易找到你的錯誤。

當你完成你的計算,你移回要回一個16位的int的權利。 如果你想獲得幻想,你也可以做你轉移之前四捨五入。

我建議你做一些閱讀,如果你在實現高效的定點真正感興趣。 http://www.digitalsignallabs.com/fp.pdf

+0

這就是我的想法。但是,你不會乘以2的冪來得到你的定點嗎?這隻涉及移位。 – 2009-09-18 15:58:34

+0

當您使用簽名的整數時,移動會變得時髦。兩者的能力更有效率,但更難以調試。如果這是第一次使用固定點,我會建議十的權力。 – patros 2009-09-18 16:10:06

+0

感謝您的回答! 我還沒有找到一個實現無浮動ADSR信封的優雅方法。 我剛試過右移樣本,結果是除以2的任意冪,因此降低了幅度 - 但我無法弄清楚如何創建一個平滑的包絡。 – 2009-09-18 18:36:27

3

this SO question的答案是在執行方面非常全面。下面是多一點的解釋比我看到那邊:

一種方法是將所有的號碼強行進入一個範圍,比如[-1.0,1.0)。然後將這些數字映射到範圍[-2^15,(2^15)-1]。例如,

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

當你乘這兩個數字你會得到32768

Temp = Half*Third; //178962432 
Result = Temp/32768; //5461 = round(1.0/6.0)*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 

這就是算術。爲了便於實施,請注意,兩個16位整數乘法需要一個32位的結果,所以溫度應該是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,那麼你有1個符號位,二進制點之前兩個位,和13後。這些分別稱爲1.15和3.13定點小數類型。您在淨空分數中交易精度。

添加和減去分數類型,只要罰款,你看出來的飽和度的作品。正如帕特洛斯所說,分割實際上取消了。所以,你所要做的

​​

,或者保持精度

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 

加和減整數,不過,並沒有天真地工作。您必須首先查看該整數是否適合您的x.y類型,製作等效的小數類型,然後繼續。

我希望這可以幫助的想法,看看在清潔的實現,其他問題的代碼。

+0

謝謝!非常全面。 – 2009-09-19 08:41:08

1

一般情況下,說你會使用簽名的16.16定點表示。這樣一個32位整數將有一個有符號的16位整數部分和一個16位小數部分。然後,我不知道用什麼語言在iPhone開發應用,但這個例子是C(Objective-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與您使用的任何語言。

在一個面向對象的語言,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) ; 
} 

這是基礎,有可能變得更加複雜,並添加三角函數等數學函數。

固定加減可與內置的+和 - 運營商和它們的變體。