Initial commit
Start experimenting with Rust. This is a little experimental, but fully functional real-time FFT program based on the FFTW library.
This commit is contained in:
commit
ff018a98e1
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
298
Cargo.lock
generated
Normal file
298
Cargo.lock
generated
Normal file
|
@ -0,0 +1,298 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a55f82cfe485775d02112886f4169bde0c5894d75e79ead7eafe7e40a25e45f7"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d117600f438b1707d4e4ae15d3595657288f8235a0eb593e80ecc98ab34e1bc"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "473fc6b38233f9af7baa94fb5852dca389e3d95b8e21c8e3719301462c5d9faf"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||
|
||||
[[package]]
|
||||
name = "failure"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"failure_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "failure_derive"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fftw"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a61025ae24e60113038885a9ad24ef67b3f4df0b4300c98678c63e8ce3297f8"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"failure",
|
||||
"fftw-sys",
|
||||
"lazy_static",
|
||||
"ndarray",
|
||||
"num-complex",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fftw-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9489c013030134b2e49cdda9bee90ff9c3c23ae5330650cb84e10b6927b9ec66"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"num-complex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.7.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d47946d458e94a1b7bcabbf6521ea7c037062c81f534615abcad76e84d4970d"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3"
|
||||
|
||||
[[package]]
|
||||
name = "matrixmultiply"
|
||||
version = "0.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcad67dcec2d58ff56f6292582377e6921afdf3bfbd533e26fb8900ae575e002"
|
||||
dependencies = [
|
||||
"rawpointer",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d"
|
||||
dependencies = [
|
||||
"adler",
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mlua"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bd179c80340f87911c17eb365ab9f890cf745ac722860d1465be5889be2853a"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"cc",
|
||||
"lazy_static",
|
||||
"num-traits",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "musiclight"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"fftw",
|
||||
"mlua",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndarray"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cf380a8af901ad627594013a3bbac903ae0a6f94e176e47e46b5bbc1877b928"
|
||||
dependencies = [
|
||||
"itertools",
|
||||
"matrixmultiply",
|
||||
"num-complex",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-complex"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9a7ab5d64814df0fe4a4b5ead45ed6c5f181ee3ff04ba344313a6c80446c5d4"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rawpointer"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebac11a9d2e11f2af219b8b8d833b76b1ea0e054aa0e8d8e9e4cbde353bdf019"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "musiclight"
|
||||
version = "0.1.0"
|
||||
authors = ["Thomas Kolb <cfr34k-git@tkolb.de>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.4"
|
||||
fftw = { version = "0.6", default-features = false, features = ["system"] }
|
||||
mlua = { version = "0.5", features = ["lua53"] }
|
3
src/config.rs
Normal file
3
src/config.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
// definitions for the FFT
|
||||
pub const BLOCK_LEN: usize = 512;
|
||||
pub const SAMP_RATE: f32 = 48000.0;
|
67
src/main.rs
Normal file
67
src/main.rs
Normal file
|
@ -0,0 +1,67 @@
|
|||
// vim: noet
|
||||
|
||||
use std::process::exit;
|
||||
|
||||
use byteorder::{NativeEndian, ReadBytesExt};
|
||||
|
||||
use mlua::Lua;
|
||||
|
||||
mod signal_processing;
|
||||
mod config;
|
||||
|
||||
use crate::signal_processing::SignalProcessing;
|
||||
|
||||
fn main()
|
||||
{
|
||||
let mut stdin = std::io::stdin();
|
||||
|
||||
// test the mlua crate
|
||||
let lua_state = Lua::new();
|
||||
|
||||
lua_state.globals().set("get_rust_value", lua_state.create_function(|_, ()| {
|
||||
Ok(3)
|
||||
}).unwrap()).unwrap();
|
||||
|
||||
let user_script = std::fs::read_to_string("test.lua").unwrap();
|
||||
lua_state.load(&user_script).exec().unwrap();
|
||||
|
||||
let lua_func_test : mlua::Function = lua_state.globals().get("test").unwrap();
|
||||
|
||||
println!("{}", lua_func_test.call::<_, u32>(123).unwrap());
|
||||
|
||||
let mut sigproc = SignalProcessing::new(config::BLOCK_LEN, config::SAMP_RATE).unwrap();
|
||||
|
||||
println!("Done! Starting main loop…");
|
||||
|
||||
// array for samples directly read from stream
|
||||
let mut samples = [0i16; config::BLOCK_LEN];
|
||||
|
||||
// main loop
|
||||
loop {
|
||||
|
||||
// read a block of samples and exit gracefully on EOF
|
||||
for sample in samples.iter_mut() {
|
||||
let res = stdin.read_i16::<NativeEndian>();
|
||||
|
||||
match res {
|
||||
Ok(s) => *sample = s,
|
||||
Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
|
||||
println!("End of stream. Exiting.");
|
||||
exit(0);
|
||||
},
|
||||
Err(e) => panic!(e)
|
||||
}
|
||||
}
|
||||
|
||||
sigproc.import_i16_mono(&samples).unwrap();
|
||||
sigproc.update_fft().unwrap();
|
||||
|
||||
let energy_bass = sigproc.get_energy_in_band( 0.0, 400.0);
|
||||
let energy_mid = sigproc.get_energy_in_band( 400.0, 4000.0);
|
||||
let energy_treble = sigproc.get_energy_in_band(4000.0, config::SAMP_RATE/2.0);
|
||||
|
||||
// dump the output
|
||||
println!("Bass: {:8.2} – Mid: {:8.2} – Treble: {:8.2}", energy_bass, energy_mid, energy_treble);
|
||||
}
|
||||
|
||||
}
|
114
src/signal_processing.rs
Normal file
114
src/signal_processing.rs
Normal file
|
@ -0,0 +1,114 @@
|
|||
// vim: noet
|
||||
|
||||
use fftw::array::AlignedVec;
|
||||
use fftw::plan::*;
|
||||
use fftw::types::*;
|
||||
use std::f32::consts::PI;
|
||||
|
||||
pub struct SignalProcessing
|
||||
{
|
||||
samp_rate: f32,
|
||||
|
||||
fft_window: Vec<f32>,
|
||||
|
||||
fft_input: AlignedVec<f32>,
|
||||
fft_output: AlignedVec<c32>,
|
||||
|
||||
fft_plan: R2CPlan32,
|
||||
|
||||
fft_absolute: Vec<f32>,
|
||||
}
|
||||
|
||||
impl SignalProcessing
|
||||
{
|
||||
fn hann_window(block_size: usize) -> Vec<f32>
|
||||
{
|
||||
let mut window = vec![0.0; block_size];
|
||||
|
||||
for i in 0..block_size {
|
||||
window[i] = (PI * (i as f32) / (block_size as f32)).sin().powi(2);
|
||||
}
|
||||
|
||||
window
|
||||
}
|
||||
|
||||
pub fn new(block_size: usize, samp_rate: f32) -> fftw::error::Result<SignalProcessing>
|
||||
{
|
||||
let freq_domain_size = block_size/2 + 1;
|
||||
|
||||
let s = SignalProcessing {
|
||||
samp_rate: samp_rate,
|
||||
fft_window: SignalProcessing::hann_window(block_size),
|
||||
fft_input: AlignedVec::new(block_size),
|
||||
fft_output: AlignedVec::new(freq_domain_size),
|
||||
fft_plan: R2CPlan::aligned(&[block_size], Flag::MEASURE)?,
|
||||
|
||||
fft_absolute: vec![0.0; freq_domain_size],
|
||||
};
|
||||
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
fn apply_window(&mut self)
|
||||
{
|
||||
self.fft_input.iter_mut()
|
||||
.zip(self.fft_window.iter())
|
||||
.for_each(|(s, w)| *s *= w);
|
||||
}
|
||||
|
||||
pub fn import_i16_stereo(&mut self, data: &[i16]) -> std::result::Result<(), &str>
|
||||
{
|
||||
if data.len() != 2*self.fft_input.len() {
|
||||
return Err("Stereo data length does not match 2x the FFT input length.");
|
||||
}
|
||||
|
||||
data.chunks_exact(2)
|
||||
.map(|channels| (channels[0] as f32 + channels[1] as f32) / 2.0 / 32768.0)
|
||||
.zip(self.fft_input.iter_mut())
|
||||
.for_each(|(c, t)| *t = c);
|
||||
|
||||
self.apply_window();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn import_i16_mono(&mut self, data: &[i16]) -> std::result::Result<(), &str>
|
||||
{
|
||||
if data.len() != self.fft_input.len() {
|
||||
return Err("Mono data length does not match the FFT input length.");
|
||||
}
|
||||
|
||||
data.iter()
|
||||
.map(|&sample| (sample as f32) / 32768.0)
|
||||
.zip(self.fft_input.iter_mut())
|
||||
.for_each(|(c, t)| *t = c);
|
||||
|
||||
self.apply_window();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_fft(&mut self) -> fftw::error::Result<()>
|
||||
{
|
||||
self.fft_plan.r2c(&mut self.fft_input, &mut self.fft_output)?;
|
||||
|
||||
for (i, abs_sample) in self.fft_absolute.iter_mut().enumerate() {
|
||||
*abs_sample = self.fft_output[i].norm();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn freq_to_idx(&self, freq: f32) -> usize
|
||||
{
|
||||
(freq * (self.fft_input.len() as f32) / self.samp_rate) as usize
|
||||
}
|
||||
|
||||
pub fn get_energy_in_band(&self, freq_start: f32, freq_end: f32) -> f32
|
||||
{
|
||||
let start_bin = self.freq_to_idx(freq_start);
|
||||
let end_bin = self.freq_to_idx(freq_end);
|
||||
|
||||
self.fft_absolute[start_bin ..= end_bin].iter().sum()
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue