result of FFT
Timon Gehr
timon.gehr at gmx.ch
Tue Jul 8 21:06:30 UTC 2025
On 7/8/25 20:11, Matthew wrote:
> Hi,
>
> I'm writing a program where I'm trying to decode DTMF tones. I already
> completed the wave file decoder and I know I'm supposed to use an FFT to
> transform the samples from time domain to frequency domain but I'm stuck
> on determining which of the DTMF frequencies are present.
>
> Consider the following, the input is the sound wave samples as an array
> of floats between -1 and +1.
>
> ```D
> void decode_sound(float[] samples)
> {
> import std.numeric;
> import std.math;
> import std.complex;
> import std.algorithm;
>
> size_t fft_size = 4096;
>
> auto f = new Fft(fft_size);
>
> foreach(ch; samples.chunks(fft_size))
> {
> auto res = f.fft(ch);
> // res is an array of 4096 complex numbers
> }
> }
> ```
>
> I can't figure out how the 4096 results of the FFT relate to the
> frequencies in the input.
>
> I tried taking the magnitude of each element, I tried taking just the
> real or imaginary parts. I plotted them but the graph doesn't look how
> I'm expecting.
>
> What do the 4096 resulting complex numbers represent?
> How should I use the result to check whether the 1209Hz, 1336Hz, 1477Hz,
> or 1633Hz tones are present in that part of the sound?
>
> Thanks,
> Matthew
Well, with `N=fft_size`, `fft` computes
```
output[j]=∑ₖe^(-2πijk/N) input[k].
```
and we know that ifft gives:
```
input[k]=∑ⱼe^(2πijk/N) output[j]/N.
```
You are trying to write your input as a linear combination of sine
waves, so we interpret `k` as time and `j/N` as frequency:
```
input[k]=∑ⱼe^(2πi k j/N)·output[j]/N.
```
Or, with `f_j[k]=e^(2πi k j/N)`:
```
input[k]=∑ⱼ(output[j]/N)·f_j[k].
```
One complication here is that both `f_j` and `output/N` are complex
functions.
Note the relationship:
```
cos(ω·t+φ) = ½·(e^(i(ωt+φ))+e^(-i(ωt+φ))) = ½·(e^(iωt)·e^φ+e^(-iωt)·e^-φ)
```
In our setup, e^(iωt) corresponds to f_j, e^(-iωt) corresponds to
f_(N-j), φ corresponds to the phase component of our outputs.
Interestingly, as your `input` signal is real, we actually know that
`output[j]` and `output[N-j]` must be conjugate to each other as this is
the only way to cancel out the imaginary parts.
This also implies that `output[N/2]` is real, which works out because
`f_(N/2)[k]=(-1)^k`.
Putting it in practice:
```d
import std;
alias K=double;
enum sample_rate=44100;
auto fToWave(R)(size_t N,R coefficients_f){
return iota(N/2+1).map!(j=>
tuple!("magnitude","frequency","phase")(
(j==N/2?1.0:2.0)*abs(coefficients_f[j]).re,
K(j)*sample_rate/N,
std.complex.log(coefficients_f[j]).im)
);
}
void main(){
enum N=4096;
auto input=wave(N,1336.0f);
auto output=fft(input);
auto coefficients_f=output.map!(o=>o/N);
//auto input2=linearCombination(N,coefficients_f,f(N)); // (slow,
just for illustration)
//assert(isClose(input,input2,1e-7,1e-7));
auto coefficients_w=fToWave(N,coefficients_f);
//auto input3=reconstructWave(N,coefficients_w); // (slow, just for
illustration)
//assert(isClose(input,input3,1e-3,1e-3));
auto sorted=coefficients_w.array.sort.retro;
foreach(magnitude,frequency,phase;sorted.until!(coeff=>coeff[0]<1e-2)){
writefln!"%s·cos(2π·%s·t%s%s)"(magnitude,frequency,text(phase).startsWith("-")?"":"+",phase);
}
}
// helper functions for illustration
auto time(size_t N)=>iota(N).map!(i=>K(i)/sample_rate);
auto wave(size_t N,K frequency,K phase=0.0f){
K angularVelocity=2*PI*frequency;
return time(N).map!(t=>cos(angularVelocity*t+phase));
}
/+
// helper functions for slow illustrations:
enum I=Complex!K(0,1);
auto f(size_t N){
return iota(N).map!(j=>iota(N).map!(k=>exp(2*PI*I*j*k/N)));
}
auto linearCombination(R,RoR)(size_t N,R coefficients,RoR functions){ //
compute ∑ⱼcoefficients[j]·functions[j]
return
iota(N).map!(k=>sum(iota(coefficients.length).map!(j=>coefficients[j]*functions[j][k])));
}
auto reconstructWave(R)(size_t N,R coefficients_w){
auto coefficients=coefficients_w.map!(coeff=>coeff.magnitude);
auto
functions=coefficients_w.map!(coeff=>wave(N,coeff.frequency,coeff.phase));
return linearCombination(N,coefficients,functions);
}
+/
```
(Note that additional processing may be helpful, as `fft` is based on
the assumption that signals repeat with a period of `N`.)
More information about the Digitalmars-d-learn
mailing list