index

Rust 音訊 Clipping 與 Peak 運算

· 3min

Conclusion

// 260530 音訊 Clipping 與 Peak 運算
fn clipping(samples: &[f32]) -> Vec<f32> {
    samples.iter().map(|&v| { v.clamp(-1.0, 1.0) }).collect::<Vec<f32>>()
}

fn peak_finding(samples: &[f32]) -> f32 {
    samples.iter().fold(0.0, | acc, &x | x.abs().max(acc))
}

fn main() {
    let samples: Vec<f32> = vec![0.5, 1.5, -2.0, -0.8, 0.1];
    let samples_clipping = clipping(&samples);
    let samples_peak = peak_finding(&samples_clipping);

    println!("original samples: {:?}", samples);
    println!("samples_clipping: {:?}", samples_clipping);
    println!("samples_peak: {:?}", samples_peak);
}

Practice

Q1Ⅰmap() Is Lazy

Iterator 在 Rust 中是惰性的(Lazy),你必須在運算鏈的最尾端加上 .collect,它才會真正把處理完的資料收集成一個新的陣列。

Q2Ⅰ只需要唯讀的情況,比起 vec 改用 slice

warning
Writing &Vec instead of &[_] Involves a New Object where a Slice Will Do

在函式參數中,如果只需要讀取資料,請用切片(Slice)&[f32] 代替向量引用(Vector Reference)&Vec<f32>

因為 vec 指向 heap 資料使用還還需要一層解引用(Dereferencing)的開銷,且一但只接受 Vec,以後有固定大小的傳統陣列(如 [f32; 1024]),就沒辦法直接傳進來

就算改成 slice,依然可以直接把 &Vec<f32> 傳進去(Rust 會自動幫你隱式轉型為 slice,稱為 Deref Coercion)。

fn clipping(samples: &[f32]) -> Vec<f32> {
    samples.iter().map(|v| { v.clamp(-1.0, 1.0) }).collect::<Vec<f32>>()
}

Q3Ⅰ使用 .fold() 找到陣列最大值(peak)

是一種累加器的概念,會把上次執行的結果值保留下來,在這個例子上,就可以去比較上一個值,看哪個比較大。

fn peak_finding(samples: &[f32]) -> f32 {
    samples.iter().fold(0.0, | acc, x | x.abs().max(acc))
}

Q4Ⅰ模式匹配(Pattern Matching)

有 2 種 deref 的方式……

第一種因為型別與輸入匹配,於是內部的 x 已經是 deref 狀態,可以直接使用

|acc, &x| { 
    // 在門口就用 & 抵消了,進來時 x 已經是抽屜裡的數字
    x.abs() 
}

第二種是在使用時再 deref,通常 rust 社群比較偏好第一種寫法。

|acc, x| { 
    // 進來時 x 是紙條 (&f32),我們用 *x 主動去抽屜拿數字
    (*x).abs() 
}

但是會發現,即使不加上 deref 編譯也會放行,主要是因為背後有 auto-deref 的機制,這與使用 dot operator 有關。