我为iPhone写了一个小软件合成器
为了进一步调整性能,我用Shark测量了我的应用程序,发现我在float / SInt16转换中浪费了很多时间。
因此我通过预先计算返回“即用型”SInt16样本的查找表来重写一些部分来绕过转换。到目前为止,此工作正常
目前我正在尝试重写一些过滤器和我的ADSR包络实现只使用整数运算但我可以使用一些tipps如何执行没有浮点数的乘法/除法。
我的目标是iPhone canonical format:
在不使用浮子的情况下,将振幅应用于最终样品有哪些好方法?
修改
到目前为止我唯一想到的是,我可以通过右移我当前的样本来除以2的幂。
inBuffer[frame] = wavetable[i % cycleLengthInSamples] >> 4;
但我想不出任何优雅的方法来创建一个平滑的ADSR信封。
EDIT2:
谢谢你所有的好答案!
我目前的做法:
这似乎有效:)
答案 0 :(得分:4)
固定点很好,因为在这种情况下你使用的是16位。最简单的方法是根据您需要的精度乘以10的幂。如果你可以使用32位整数作为中间体,你应该能够获得不错的精度。最后,您可以根据需要转换回16位int,舍入或截断。
编辑: 您想要向左移动,以使值更大。将移位结果存储在具有更高精度的类型中(32位或64位,具体取决于您的需要)。如果您使用签名类型
,简单移位将无效注意您是否乘以或除以两个固定点数。乘以(a * n)*(b n),你将得到 b n ^ 2而不是 b n。除法是(a n)/(b n),它是(a / b)而不是((a n)/ b)。这就是为什么我建议使用10的幂,如果你不熟悉固定点,很容易找到你的错误。
当你完成计算后,你会向右移回到16位int。 如果你想获得幻想,你也可以在转移前进行四舍五入。
如果你真的对实现有效的定点感兴趣,我建议你做一些阅读。 http://www.digitalsignallabs.com/fp.pdf
答案 1 :(得分: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
当你将这两个数相乘时,你得到了
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
这就是算术。对于实现,请注意两个16位整数的乘法需要32位结果,因此Temp应为32位。此外,32768在16位变量中无法表示,因此请注意编译器将生成32位立即数。正如您已经注意到的那样,您可以转换为乘以2除以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个符号位,2位在二进制点之前,13位在之后。这些分别称为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
然而,添加和减去整数并不天真。你必须首先看看整数是否适合你的x.y类型,制作等效的小数类型,然后继续。
我希望这有助于理念,查看其他问题中的代码以实现干净的实现。
答案 2 :(得分:1)
查看描述快速乘法算法的页面。
答案 3 :(得分: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) ;
}
这是基础知识,可以更复杂并添加trig和其他数学函数。
修复加法和减法与内置+和 - 运算符及其变体一起使用。