📈
Rustでフーリエ変換(DTFT)
モチベーション
音声技術の勉強として、どシンプルな離散フーリエ変換(DTFT)を実装。
高速化(FFT)は未考慮。FFTの記事も別に書く予定。 (→ こちらに書きました)
概要
この記事では「フーリエ変換 = 時系列データ(x) -> 周波数(f[Hz]) の変換」とする。
-
: 周波数X_f [Hz]におけるフーリエ変換結果f -
: 周波数[Hz]f -
: 入力フレームの個数N -
: 各入力フレームの値[bit]x_k -
: 角周波数\omega
= 1秒間に進む角度(ω=2πで1周) -
: 各入力フレーム時点での再生時間[s]t -
: サンプリング周波数[Hz]f_s
= 1秒間に収録されるフレーム数
補足
実装
- 結果データのサイズは、ナイキスト周波数(=
)とするf_s/2 - 折り返し波による競合を防ぐため (詳しくはこちら)
- 高速化への考慮ゼロ
src/main.rs
use num::complex::Complex;
use std::f64::consts::PI;
pub fn dtft(frames: Vec<f64>, fs: i64) -> Vec<Complex<f64>> {
let mut rslt: Vec<Complex<f64>> = Vec::new();
// 1 <= f <= fs/2
for f in 1..=(fs / 2) {
let mut sigma: Complex<f64> = Complex::new(0.0, 0.0);
// 0 <= k <= N-1
for (k, xk) in frames.iter().enumerate() {
// t = k / fs
let t: f64 = (k as f64) / (fs as f64);
// ω = 2πf
let w = 2.0 * PI * (f as f64);
// e^(-iωt)
let exp = Complex::new(0.0, -w * t).exp();
// X(f) = ∑xk*e^(-iωt)
sigma += xk * exp;
}
rslt.push(sigma);
}
return rslt;
}
検証
以下を実行して、期待通りhz(=2000)で最大値になればOK。
src/main.rs
// --snip--
fn main() {
// create sin_curve frames
let flame_len = 1000;
let samplerate = 16000;
let hz = 2000;
let sin_curve: Vec<f64> = (0..flame_len)
.map(|a| (a as f64) * 2.0 * PI * (hz as f64) / (samplerate as f64))
.map(|a| a.sin())
.collect();
let rslt = dtft(sin_curve, samplerate);
let (max_index, max) =
rslt.iter()
.enumerate()
.fold((usize::MIN, f64::MIN), |(i_a, a), (i_b, &b)| {
if b.norm() > a {
(i_b, b.norm())
} else {
(i_a, a)
}
});
println!("rslt len: {:?}", rslt.len()); // = fs/2
println!("max rslt f: {:?}", max_index + 1); // 1 <= f <= fs/2
println!("max rslt: {:?}", max);
}
成功例👌
xx % time cargo run
Compiling fft_rs v0.1.0 (/Users/xx)
Finished dev [unoptimized + debuginfo] target(s) in 0.49s
Running `target/debug/xx`
rslt len: 8000
max rslt f: 2000
max rslt: 499.99999999999994
cargo run 0.89s user 0.16s system 68% cpu 1.527 total
補足
flame_lenを増やせば(高速化していないので遅くなりますが) max rsltの値がどんどん大きくなります。
↓イメージ
高速化も含めた実装記事も書きました。
それでは。
Discussion