有没有人将它Apple FFT
用于iPhone应用程序,或者知道在哪里可以找到示例应用程序的使用方法?我知道苹果发布了一些示例代码,但是我不确定如何将其实现到实际项目中。
有没有人将它Apple FFT
用于iPhone应用程序,或者知道在哪里可以找到示例应用程序的使用方法?我知道苹果发布了一些示例代码,但是我不确定如何将其实现到实际项目中。
Answers:
我刚刚获得了适用于iPhone项目的FFT代码:
您可能还需要从info.plist中删除一个条目,该条目告诉项目加载xib,但是我90%的确定您无需为此烦恼。
注意:程序输出到控制台,结果显示为0.000,这不是错误-只是非常非常快
这段代码确实很愚蠢;它被慷慨地发表了评论,但是这些评论并没有使生活变得更加轻松。
基本上,它的核心是:
vDSP_fft_zrip(setupReal, &A, stride, log2n, FFT_FORWARD);
vDSP_fft_zrip(setupReal, &A, stride, log2n, FFT_INVERSE);
在n个实数浮点数上进行FFT,然后反转以返回到我们开始的地方。ip代表就地,这意味着&A被覆盖。这就是所有这种特殊打包Malarkey的原因-这样我们就可以将返回值压缩为与发送值相同的空间。
为了提供一些见解(例如,如下所示:为什么我们首先要使用此功能?),假设我们要对麦克风输入执行音高检测,并且我们对其进行了设置,以便每次都触发一些回调麦克风进入1024个浮动状态。假设麦克风的采样率为44.1kHz,则约为44帧/秒。
因此,我们的时间窗口是1024个样本的持续时间,即1/44 s。
因此,我们将A封装为带有麦克风的1024个浮点,设置log2n = 10(2 ^ 10 = 1024),预先计算一些线轴(setupReal),然后:
vDSP_fft_zrip(setupReal, &A, stride, log2n, FFT_FORWARD);
现在A将包含n / 2个复数。这些代表n / 2个频率段:
bin [1] .idealFreq = 44Hz-即我们可以可靠地检测到的最低频率是该窗口内的一个完整波,即44Hz波。
bin [2] .idealFreq = 2 * 44Hz
等等
bin [512] .idealFreq = 512 * 44Hz-我们可以检测到的最高频率(称为奈奎斯特频率)是每对点代表一个波,即窗口内的512个完整波,即512 * 44Hz,或者: n / 2 * bin [1] .idealFreq
实际上,有一个额外的Bin,Bin [0],通常称为“ DC Offset”。碰巧Bin [0]和Bin [n / 2]将始终具有复数分量0,因此A [0] .realp用于存储Bin [0],而A [0] .imagp用于存储Bin [ n / 2]
每个复数的大小就是在该频率附近振动的能量。
因此,正如您所看到的,它不是一个非常好的音高检测器,因为它没有足够精细的粒度。有一个狡猾的技巧,即 利用帧之间的相位变化从FFT Bin中提取精确频率,以获得给定bin的精确频率。
好的,现在进入代码:
注意vDSP_fft_zrip中的“ ip”,=“就位”,即输出覆盖A(“ r”表示它接受实际输入)
查看有关vDSP_fft_zrip的文档,
实数据以拆分复数形式存储,奇数实数存储在拆分复数形式的虚部,偶数实数存储在实数侧。
这可能是最难理解的事情。在整个过程中,我们一直使用相同的容器(&A)。因此,一开始我们要用n个实数填充它。FFT之后,它将保留n / 2个复数。然后将其放入逆变换中,并希望得出原始的n个实数。
现在A的结构为其复杂值设置。因此,vDSP需要标准化如何将实数打包到其中。
所以首先我们生成n个实数:1、2,...,n
for (i = 0; i < n; i++)
originalReal[i] = (float) (i + 1);
接下来,我们将它们打包为A,作为n / 2个复数#:
// 1. masquerades n real #s as n/2 complex #s = {1+2i, 3+4i, ...}
// 2. splits to
// A.realP = {1,3,...} (n/2 elts)
// A.compP = {2,4,...} (n/2 elts)
//
vDSP_ctoz(
(COMPLEX *) originalReal,
2, // stride 2, as each complex # is 2 floats
&A,
1, // stride 1 in A.realP & .compP
nOver2); // n/2 elts
您确实需要查看如何分配A来实现这一点,也许可以在文档中查询COMPLEX_SPLIT。
A.realp = (float *) malloc(nOver2 * sizeof(float));
A.imagp = (float *) malloc(nOver2 * sizeof(float));
接下来,我们进行预计算。
适用于数学的快速DSP课程: 傅立叶理论花了很长时间才能引起您的注意(我几年来一直在研究它)
一个类固醇是:
z = exp(i.theta) = cos(theta) + i.sin(theta)
也就是在复杂平面上单位圆上的一点。
当您将复数相乘时,角度会相加。因此z ^ k会一直围绕单位圆跳动;可以在角度k.theta处找到z ^ k
选择z1 = 0 + 1i,即从实轴开始的四分之一转,并注意z1 ^ 2 z1 ^ 3 z1 ^ 4各自又旋转了四分之一转,因此z1 ^ 4 = 1
选择z2 = -1,即半圈。同样z2 ^ 4 = 1,但是z2在这一点已经完成了2个周期(z2 ^ 2也= 1)。因此,您可以将z1视为基频,将z2视为一次谐波
类似地,z3 =“四分之三的转折点”,即-i正好完成了3个周期,但实际上每次前进3/4均与每次后退1/4相同
即z3只是z1但方向相反-称为别名
z2是最高有意义的频率,因为我们选择了4个样本来保持全波。
您可以将任何4点信号表示为z0 z1和z2的线性组合, 即,将其投影到这些基矢量上
但是我听到你问“将信号投射到一个类固醇意味着什么?”
您可以这样想:针绕着类固醇旋转,因此在样本k处,针指向k.theta方向,长度为signal [k]。恰好与类固醇频率匹配的信号会在某个方向上凸出结果形状。因此,如果将所有贡献加起来,就会得到一个很强的合成向量。如果频率接近匹配,则凸出部分将变小,并会在圆周围缓慢移动。对于与频率不匹配的信号,这些贡献将相互抵消。
http://complextoreal.com/tutorials/tutorial-4-fourier-analysis-made-easy-part-1/ 将帮助您获得直观的理解。
但是要点是:如果我们选择将1024个样本投影到{z0,...,z512}上,我们将通过z512到z0进行预计算,这就是该预计算步骤。
请注意,如果您使用真实代码执行此操作,则可能希望在应用程序加载时执行一次,并在退出时调用互补释放函数。不要做很多次-价格昂贵。
// let's say log2n = 8, so n=2^8=256 samples, or 'harmonics' or 'terms'
// if we pre-calculate the 256th roots of unity (of which there are 256)
// that will save us time later.
//
// Note that this call creates an array which will need to be released
// later to avoid leaking
setupReal = vDSP_create_fftsetup(log2n, FFT_RADIX2);
值得注意的是,如果将log2n设置为例如8,则可以将这些预先计算的值放入任何使用分辨率<= 2 ^ 8的fft函数中。因此(除非您不希望实现最终的内存优化),只需创建一组您需要的最高分辨率,然后将其用于所有操作即可。
现在是实际的转换,利用了我们刚刚计算出的内容:
vDSP_fft_zrip(setupReal, &A, stride, log2n, FFT_FORWARD);
此时,A将包含n / 2个复数,只有第一个实际上是伪装成复数的两个实数(DC偏移,Nyquist#)。文档概述对此包装进行了说明。它非常整洁-基本上,它可以将转换的(复杂)结果打包为与(实际但奇怪打包的)输入相同的内存占用量。
vDSP_fft_zrip(setupReal, &A, stride, log2n, FFT_INVERSE);
然后再次返回...我们仍然需要从A中解压缩原始数组。然后我们进行比较,以检查是否已经完全恢复了我们的开始,释放了预先计算的线轴并完成了!
可是等等!打开包装之前,需要完成最后一件事:
// Need to see the documentation for this one...
// in order to optimise, different routines return values
// that need to be scaled by different amounts in order to
// be correct as per the math
// In this case...
scale = (float) 1.0 / (2 * n);
vDSP_vsmul(A.realp, 1, &scale, A.realp, 1, nOver2);
vDSP_vsmul(A.imagp, 1, &scale, A.imagp, 1, nOver2);
这是一个真实的示例:使用Accelerate的vDSP fft例程对Remote IO音频单元的输入进行自相关的c ++代码段。利用这个框架是相当复杂的,但文件是不是太糟糕。
OSStatus DSPCore::initialize (double _sampleRate, uint16_t _bufferSize) {
sampleRate = _sampleRate;
bufferSize = _bufferSize;
peakIndex = 0;
frequency = 0.f;
uint32_t maxFrames = getMaxFramesPerSlice();
displayData = (float*)malloc(maxFrames*sizeof(float));
bzero(displayData, maxFrames*sizeof(float));
log2n = log2f(maxFrames);
n = 1 << log2n;
assert(n == maxFrames);
nOver2 = maxFrames/2;
A.realp = (float*)malloc(nOver2 * sizeof(float));
A.imagp = (float*)malloc(nOver2 * sizeof(float));
FFTSetup fftSetup = vDSP_create_fftsetup(log2n, FFT_RADIX2);
return noErr;
}
void DSPCore::Render(uint32_t numFrames, AudioBufferList *ioData) {
bufferSize = numFrames;
float ln = log2f(numFrames);
//vDSP autocorrelation
//convert real input to even-odd
vDSP_ctoz((COMPLEX*)ioData->mBuffers[0].mData, 2, &A, 1, numFrames/2);
memset(ioData->mBuffers[0].mData, 0, ioData->mBuffers[0].mDataByteSize);
//fft
vDSP_fft_zrip(fftSetup, &A, 1, ln, FFT_FORWARD);
// Absolute square (equivalent to mag^2)
vDSP_zvmags(&A, 1, A.realp, 1, numFrames/2);
bzero(A.imagp, (numFrames/2) * sizeof(float));
// Inverse FFT
vDSP_fft_zrip(fftSetup, &A, 1, ln, FFT_INVERSE);
//convert complex split to real
vDSP_ztoc(&A, 1, (COMPLEX*)displayData, 2, numFrames/2);
// Normalize
float scale = 1.f/displayData[0];
vDSP_vsmul(displayData, 1, &scale, displayData, 1, numFrames);
// Naive peak-pick: find the first local maximum
peakIndex = 0;
for (size_t ii=1; ii < numFrames-1; ++ii) {
if ((displayData[ii] > displayData[ii-1]) && (displayData[ii] > displayData[ii+1])) {
peakIndex = ii;
break;
}
}
// Calculate frequency
frequency = sampleRate / peakIndex + quadInterpolate(&displayData[peakIndex-1]);
bufferSize = numFrames;
for (int ii=0; ii<ioData->mNumberBuffers; ++ii) {
bzero(ioData->mBuffers[ii].mData, ioData->mBuffers[ii].mDataByteSize);
}
}
getMaxFramesPerSlice()
无法进行设置#define
,因为每次运行都会有所不同。该方法实际上是相应音频单元属性访问器的包装。该代码将输入置零,因为将相同的缓冲区传递到设备的输出-将其置零可防止反馈环路。
vDSP_zvmags
不应将其应用于元素0,因为它的虚部实际上是Nyquist存储桶的实部。您不应该只是A.realp[0]
和A.imagp[0]
,而不是bzero
A.imagp[0]
吗?
虽然我会说Apple的FFT框架很快...您需要知道FFT的工作原理才能获得准确的音高检测(即,计算每个连续FFT的相位差以便找到准确的音高,而不是音高最主要的垃圾箱)。
我不知道有什么帮助,但是我从调谐器应用程序(musicianskit.com/developer.php)上传了我的Pitch Detector对象。还有一个示例xCode 4项目可供下载(因此,您可以看到实现的工作原理)。
我正在上载FFT实施示例-请保持关注,一旦发生,我将对其进行更新。
祝您编码愉快!
这是另一个真实的示例:https : //github.com/krafter/DetectingAudioFrequency