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:
Thomas Kolb 2021-02-16 22:44:31 +01:00
commit ff018a98e1
6 changed files with 495 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

298
Cargo.lock generated Normal file
View 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
View 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
View 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
View 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
View 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()
}
}