Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add chroma filter generator stuff #66

Merged
merged 1 commit into from
Jun 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions bliss-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ edition = "2018"
build-ffmpeg = ["ffmpeg4/build"]

[dependencies]
ripemd160 = "*"
ripemd160 = "0.9.0"

[dependencies.aubio-rs]
git = "https://github.com/katyo/aubio-rs"

[dependencies.aubio-lib]
git = "https://github.com/katyo/aubio-rs"
git = "https://github.com/Polochon-street/aubio-rs"
optional = true
branch = "fix-python-version"

[dependencies.ffmpeg4]
# Like this until https://github.com/bacek/rust-ffmpeg4/pull/5 is merged
Expand Down
155 changes: 155 additions & 0 deletions bliss-rs/src/chroma.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
#[cfg(feature = "aubio-lib")]
extern crate aubio_lib;

use crate::utils::hz_to_octs;

// chroma(22050, n_fft=5, n_chroma=12)
pub fn chroma_filter(sample_rate: u32, n_fft: u32, n_chroma: u32, tuning: f64) -> Vec<Vec<f32>> {
let step = sample_rate as f64 / n_fft as f64;
let ctroct = 5.0;
let octwidth = 2;

// [4410.0, 8820.0, 13230.0, 17640.0]
let frequencies = (1..n_fft).map(|i| i as f64 * step).collect::<Vec<f64>>();

// [87.90243872, 99.90243872, 106.92198873, 111.90243872]
let temp_freq_bins = hz_to_octs(&frequencies, tuning, n_chroma)
.iter()
.map(|i| i * n_chroma as f64)
.collect::<Vec<f64>>();
// [69.90243872, 87.90243872, 99.90243872, 106.92198873, 111.90243872]
let mut freq_bins = vec![temp_freq_bins[0] - 1.5 * n_chroma as f64];
freq_bins.extend_from_slice(temp_freq_bins.as_slice());

// [18., 12., 7.01955001, 4.98044999, 1.]
let mut binwidth_bin = (&freq_bins[1..freq_bins.len()])
.iter()
.zip(&freq_bins[0..freq_bins.len() - 1])
.map(|(x, y)| (x - y).max(1.0))
.collect::<Vec<f64>>();
binwidth_bin.extend_from_slice(&[1.0]);

// [[69.90243872, 87.90243872, 99.90243872, 106.92198873, 111.90243872]
// ...
// [58.90243872, 76.90243872, 88.90243872, 95.92198873, 100.90243872]]
let d = (0..n_chroma)
.map(|i| freq_bins.iter().map(|f| f - i as f64).collect::<Vec<f64>>())
.collect::<Vec<Vec<f64>>>();

let n_chroma2 = (n_chroma as f64 / 2.0).round() as u32;

// [[-2.09756128, 3.90243872, 3.90243872, -1.07801127, 3.90243872]
// ...
// [-1.09756128, 4.90243872, 4.90243872, -0.07801127, 4.90243872]]
let d = d
.iter()
.map(|s| {
s.iter()
.map(|v| {
(v + n_chroma2 as f64 + 10. * n_chroma as f64) % n_chroma as f64
- n_chroma2 as f64
})
.collect::<Vec<f64>>()
})
.collect::<Vec<Vec<f64>>>();

// [[9.73206457e-01, 8.09357725e-01, 5.38948409e-01, 9.10555920e-01, 5.91880948e-14],
// ...
// [9.92591525e-01, 7.16193969e-01, 3.76996585e-01, 9.99509430e-01, 1.33172632e-21]]
let wts = d
.iter()
.map(|s| {
s.iter()
.zip(&binwidth_bin)
.map(|(v, b)| (-0.5 * ((2.0 * v / b) as f64).powf(2.0)).exp())
.collect::<Vec<f64>>()
})
.collect::<Vec<Vec<f64>>>();

// Normalize by computing the l2-norm over the columns
let mut length = vec![0.; n_fft as usize];
for i in 0..(n_fft as usize) {
let mut vec = vec![0.; n_fft as usize];
for x in &wts {
vec.push(x[i].powf(2.0));
}
length[i] = vec.iter().map(|x| x).sum::<f64>().sqrt();
}

// [3.01362739e-01, 2.70504696e-01, 2.17863058e-01, 4.33578438e-01, 5.89176039e-14]
// ...
// [3.07365511e-01, 2.39367373e-01, 1.52396088e-01, 4.75935336e-01, 1.32564030e-21]
let wts = wts
.iter()
.map(|s| {
s.iter()
.enumerate()
.map(|(i, v)| v / (&length)[i])
.collect::<Vec<f64>>()
})
.collect::<Vec<Vec<f64>>>();

// [0.91840203, 0.50873844, 0.25104525, 0.14790657, 0.09647968]
let scaling = &freq_bins
.iter()
.map(|f| (-0.5 * (((f / n_chroma as f64 - ctroct) / octwidth as f64).powf(2.0))).exp())
.collect::<Vec<f64>>();

// [[2.76772150e-01, 1.37616138e-01, 5.46934868e-02, 6.41290987e-02, 5.68435153e-15]
// ...
// [2.82285109e-01, 1.21775385e-01, 3.82583145e-02, 7.03939621e-02, 1.27897351e-22]]
let mut wts = wts
.iter()
.map(|s| {
s.iter()
.zip(scaling)
.map(|(v, f)| v * f)
.collect::<Vec<f64>>()
})
.collect::<Vec<Vec<f64>>>();
// "if base_c"
// [[2.42245481e-01, 1.68118894e-01, 9.81821393e-02, 1.84251933e-02, 1.88395920e-02]
// ...
// [2.56393048e-01, 1.61695478e-01, 8.76171131e-02, 3.28090351e-02, 6.89899611e-05]]
wts.rotate_left(3);

let non_aliased = 1 + n_fft / 2;

wts.iter()
.map(|s| {
s.iter()
.take(non_aliased as usize)
.map(|f| *f as f32)
.collect::<Vec<f32>>()
})
.collect::<Vec<Vec<f32>>>()
}

mod test {
use super::*;

#[test]
fn test_generate_chroma() {
let expected_chroma: Vec<Vec<f32>> = vec![
vec![0.24224548, 0.16811889, 0.09818214],
vec![0.22936151, 0.17000881, 0.10144266],
vec![0.24518087, 0.16721014, 0.09663921],
vec![0.25887551, 0.15995214, 0.08488495],
vec![0.2699813, 0.14881742, 0.06874682],
vec![0.27810881, 0.13466469, 0.05133566],
vec![0.28296593, 0.11851955, 0.03534518],
vec![0.28437531, 0.10480594, 0.0246752],
vec![0.28228511, 0.12177538, 0.03825831],
vec![0.27677215, 0.13761614, 0.05469349],
vec![0.26803725, 0.151257, 0.07209248],
vec![0.25639305, 0.16169548, 0.08761711],
];
let filter = chroma_filter(22050, 5, 12, 0.0);

for (column1, column2) in expected_chroma.iter().zip(filter.iter()) {
for (val1, val2) in column1.iter().zip(column2) {
assert!(0.0001 > (val1 - val2).abs());
}
}
}
}
4 changes: 2 additions & 2 deletions bliss-rs/src/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,10 @@ mod tests {
let song = decode_song(path).unwrap();
let mut hasher = Ripemd160::new();
for sample in song.sample_array.iter() {
hasher.input(sample.to_le_bytes().to_vec());
hasher.update(sample.to_le_bytes().to_vec());
}

assert_eq!(expected_hash, hasher.result().as_slice());
assert_eq!(expected_hash, hasher.finalize().as_slice());
}

#[test]
Expand Down
1 change: 1 addition & 0 deletions bliss-rs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// temporarily pub
pub mod analyze;
pub mod chroma;
pub mod decode;
pub mod misc;
pub mod temporal;
Expand Down
15 changes: 15 additions & 0 deletions bliss-rs/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ pub fn geometric_mean(input: &[f32]) -> f32 {
mean.exp()
}

pub fn hz_to_octs(frequencies: &[f64], tuning: f64, bins_per_octave: u32) -> Vec<f64> {
let a440 = 440.0 * (2_f64.powf(tuning / bins_per_octave as f64) as f64);

frequencies.iter().map(|freq| (freq / (a440 / 16.)).log2()).collect()
}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -62,4 +68,13 @@ mod tests {
let numbers = vec![4.0, 1.0, 0.03125];
assert_eq!(0.5, geometric_mean(&numbers));
}

#[test]
fn test_hz_to_octs() {
let frequencies = vec![32., 64., 128., 256.];
let expected = vec![0.16864029, 1.16864029, 2.16864029, 3.16864029];

let octs = hz_to_octs(&frequencies, 0.5, 10);
octs.iter().zip(expected.iter()).for_each(|(x, y) | assert!(0.0001 > (x - y).abs()));
}
}