Rust Sine Wave 產生器
· 3min
Conclusion
與 Rust 播放 WAV 檔與即時音量控制 不同的是,這次 source 是自已產生,且 decoder 的部分也要自行處理。
以下是版本一:
// 260602 Sine Wave 產生器
use cpal::{SampleFormat, Sample, FromSample};
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
fn sine_gen<T>(data: &mut [T], _: &cpal::OutputCallbackInfo)
where
T: Sample + FromSample<f32>
{
let sample_rate = 48000.0;
let freq = 440.0;
for (idx, sample) in data.iter_mut().enumerate() {
let t = idx as f32 / sample_rate;
let sig: T = T::from_sample((2.0 * std::f32::consts::PI * freq * t).sin());
*sample = sig;
}
}
fn main() {
let host = cpal::default_host();
let device = host.default_output_device().expect("No availible device");
let supported_config = device
.default_output_config()
.expect("Error while querying configs");
let sample_format = supported_config.sample_format();
let config = supported_config.into();
let err_fn = |err| eprintln!("Error: {err}");
let stream = match sample_format {
SampleFormat::F32 => device.build_output_stream(&config, sine_gen::<f32>, err_fn, None),
SampleFormat::I16=> device.build_output_stream(&config, sine_gen::<i16>, err_fn, None),
SampleFormat::U16=> device.build_output_stream(&config, sine_gen::<u16>, err_fn, None),
other_format => panic!("Unsupported sample format '{:?}'", other_format),
}
.unwrap();
stream.play().unwrap();
std::thread::sleep(std::time::Duration::from_secs(5));
}
會發現雖然用 idx 來製造 t 可行,但不會是純 sine wave,主要還是因為 idx 會從 0 開始算,導致產生「破音」。
// 取得真實的取樣率
let sample_rate = config.sample_rate.0 as f32;
let freq = 440.0;
let stream = match sample_format {
SampleFormat::F32 => {
// 1. 在傳入閉包「之前」,宣告一個可變的狀態變數
let mut sample_clock = 0f32;
device.build_output_stream(
&config,
// 2. 使用 move 關鍵字,把 sample_clock 的所有權「吃」進閉包裡
move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
for sample in data.iter_mut() {
// 3. 推進時鐘(加上取餘數防止時間無限大導致浮點數失真)
sample_clock = (sample_clock + 1.0) % sample_rate;
let t = sample_clock / sample_rate;
*sample = (2.0 * std::f32::consts::PI * freq * t).sin();
}
},
err_fn,
None,
)
},
// (I16, U16 的邏輯一模一樣,只需宣告各自的 clock 並轉型即可)
// ...
other_format => panic!("Unsupported sample format '{:?}'", other_format),
}.unwrap();Practice
cpal
Links
cargo add cpalhost即本機,給予各種可存取的外部音訊裝置(device)device具有 IO 的音訊裝置stream音訊流,需選擇「流向」的裝置
From / Into
From 可以多種不同型別都換為一種(例如 String), Into 則是比較方便的寫法,但因為沒有指定型別,編譯器不會知道要哪一種,所以通常需要變數手動指定。
建立 From 之後 Into 也會隨之成立。
let my_string = String::from("hello");原始碼:
// 這是標準函式庫中,為 String 實作 From<&str> 的原碼
#[stable(feature = "rust1", since = "1.0.0")]
impl From<&str> for String {
#[inline]
fn from(s: &str) -> String {
s.to_owned()
}
}回到實作:
// 意思是:為 StreamConfig 這個型別,實作 From<SupportedStreamConfig> 特徵
impl From<SupportedStreamConfig> for StreamConfig {
// 這裡的 Self,就是 StreamConfig 的替身!
fn from(conf: SupportedStreamConfig) -> Self {
// 內部邏輯:把 conf 拆開,重新組裝成 StreamConfig
StreamConfig {
channels: conf.channels(),
sample_rate: conf.sample_rate(),
buffer_size: conf.buffer_size().clone(),
}
}
}