From a4f6914e5f92140fca8e6da99f6746d1e86dd002 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Sun, 5 Sep 2021 21:03:16 +0200 Subject: [PATCH 1/5] code improvements (clippy, rustfmt, rustdoc) --- examples/audio_input_beat_detection.rs | 49 +++++++++++++-------- examples/minimal.rs | 21 ++++----- examples/ws2812_spi_light_on_beat.rs | 60 ++++++++++++++++---------- src/lib.rs | 30 ++++++++++--- src/record.rs | 11 +++-- src/strategies/mod.rs | 2 +- src/strategies/window_stats.rs | 4 +- 7 files changed, 112 insertions(+), 65 deletions(-) diff --git a/examples/audio_input_beat_detection.rs b/examples/audio_input_beat_detection.rs index b7f16bf..39b7bce 100644 --- a/examples/audio_input_beat_detection.rs +++ b/examples/audio_input_beat_detection.rs @@ -21,10 +21,10 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +use beat_detector::StrategyKind; use cpal::Device; -use std::collections::{BTreeMap}; +use std::collections::BTreeMap; use std::io::stdin; -use beat_detector::StrategyKind; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; @@ -34,21 +34,24 @@ fn main() { ctrlc::set_handler(move || { eprintln!("Stopping recording"); recording_cpy.store(false, Ordering::SeqCst); - }).expect("Ctrl-C handler doesn't work"); + }) + .expect("Ctrl-C handler doesn't work"); let devs = beat_detector::record::audio_input_device_list(); - if devs.is_empty() { panic!("No audio input devices found!") } - let dev = if devs.len() > 1 { select_input_device(devs) } else { devs.into_iter().next().unwrap().1 }; + if devs.is_empty() { + panic!("No audio input devices found!") + } + let dev = if devs.len() > 1 { + select_input_device(devs) + } else { + devs.into_iter().next().unwrap().1 + }; let strategy = select_strategy(); let on_beat = |info| { println!("Found beat at {:?}ms", info); }; - let handle = beat_detector::record::start_listening( - on_beat, - Some(dev), - strategy, - recording, - ).unwrap(); + let handle = + beat_detector::record::start_listening(on_beat, Some(dev), strategy, recording).unwrap(); handle.join().unwrap(); } @@ -61,8 +64,12 @@ fn select_input_device(devs: BTreeMap) -> Device { println!("Select audio device: input device number and enter:"); let mut input = String::new(); while stdin().read_line(&mut input).unwrap() == 0 {} - let input = input.trim().parse::().expect("Input must be a valid number!"); - devs.into_iter().enumerate() + let input = input + .trim() + .parse::() + .expect("Input must be a valid number!"); + devs.into_iter() + .enumerate() .filter(|(i, _)| *i == input as usize) .map(|(_i, (_name, dev))| dev) .take(1) @@ -72,16 +79,22 @@ fn select_input_device(devs: BTreeMap) -> Device { fn select_strategy() -> StrategyKind { println!("Available beat detection strategies:"); - StrategyKind::values().into_iter().enumerate().for_each(|(i, s)| { - println!(" [{}] {} - {}", i, s.name(), s.description()); - }); + StrategyKind::values() + .into_iter() + .enumerate() + .for_each(|(i, s)| { + println!(" [{}] {} - {}", i, s.name(), s.description()); + }); println!("Select strategy: input id and enter:"); let mut input = String::new(); while stdin().read_line(&mut input).unwrap() == 0 {} - let input = input.trim().parse::().expect("Input must be a valid number!"); + let input = input + .trim() + .parse::() + .expect("Input must be a valid number!"); match input { 0 => StrategyKind::LPF, 1 => StrategyKind::Spectrum, _ => panic!("Invalid strategy!"), } -} \ No newline at end of file +} diff --git a/examples/minimal.rs b/examples/minimal.rs index 7f12c65..06a123b 100644 --- a/examples/minimal.rs +++ b/examples/minimal.rs @@ -23,8 +23,8 @@ SOFTWARE. */ //! Minimum example on how to use this library. Sets up the "callback loop". -use cpal::Device; use beat_detector::StrategyKind; +use cpal::Device; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; @@ -36,7 +36,8 @@ fn main() { ctrlc::set_handler(move || { eprintln!("Stopping recording"); recording_cpy.store(false, Ordering::SeqCst); - }).unwrap(); + }) + .unwrap(); let dev = select_input_device(); let strategy = select_strategy(); @@ -44,22 +45,22 @@ fn main() { println!("Found beat at {:?}ms", info); }; // actually start listening in thread - let handle = beat_detector::record::start_listening( - on_beat, - Some(dev), - strategy, - recording, - ).unwrap(); + let handle = + beat_detector::record::start_listening(on_beat, Some(dev), strategy, recording).unwrap(); handle.join().unwrap(); } fn select_input_device() -> Device { // todo implement user selection - beat_detector::record::audio_input_device_list().into_iter().next().expect("At least one audio input device must be available.").1 + beat_detector::record::audio_input_device_list() + .into_iter() + .next() + .expect("At least one audio input device must be available.") + .1 } fn select_strategy() -> StrategyKind { // todo implement user selection StrategyKind::Spectrum -} \ No newline at end of file +} diff --git a/examples/ws2812_spi_light_on_beat.rs b/examples/ws2812_spi_light_on_beat.rs index 9a27909..d136857 100644 --- a/examples/ws2812_spi_light_on_beat.rs +++ b/examples/ws2812_spi_light_on_beat.rs @@ -21,17 +21,16 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +use beat_detector::StrategyKind; use cpal::Device; -use std::collections::{BTreeMap}; +use std::collections::BTreeMap; use std::io::stdin; -use beat_detector::StrategyKind; +use std::ops::Add; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; -use std::time::{Instant, Duration}; -use std::ops::Add; -use ws2818_rgb_led_spi_driver::adapter_spi::WS28xxSpiAdapter; +use std::time::{Duration, Instant}; use ws2818_rgb_led_spi_driver::adapter_gen::WS28xxAdapter; - +use ws2818_rgb_led_spi_driver::adapter_spi::WS28xxSpiAdapter; // LED steps per second pub const ANIMATION_FREQUENCY: u64 = 90; // in Hz @@ -52,23 +51,27 @@ fn main() { ctrlc::set_handler(move || { eprintln!("Stopping recording"); recording_cpy.store(false, Ordering::SeqCst); - }).expect("Ctrl-C handler doesn't work"); + }) + .expect("Ctrl-C handler doesn't work"); let devs = beat_detector::record::audio_input_device_list(); - if devs.is_empty() { panic!("No audio input devices found!") } - let dev = if devs.len() > 1 { select_input_device(devs) } else { devs.into_iter().next().unwrap().1 }; + if devs.is_empty() { + panic!("No audio input devices found!") + } + let dev = if devs.len() > 1 { + select_input_device(devs) + } else { + devs.into_iter().next().unwrap().1 + }; let strategy = select_strategy(); let anim_t = anim.clone(); let on_beat = move |info| { println!("Found beat at {:?}ms", info); anim_t.lock().unwrap().add_next_light_impulse(); }; - let handle = beat_detector::record::start_listening( - on_beat, - Some(dev), - strategy, - recording.clone(), - ).unwrap(); + let handle = + beat_detector::record::start_listening(on_beat, Some(dev), strategy, recording.clone()) + .unwrap(); while recording.load(Ordering::SeqCst) { let next_timestamp = Instant::now().add(Duration::from_millis(ANIMATION_FREQUENCY_MS)); @@ -93,8 +96,12 @@ fn select_input_device(devs: BTreeMap) -> Device { println!("Select audio device: input device number and enter:"); let mut input = String::new(); while stdin().read_line(&mut input).unwrap() == 0 {} - let input = input.trim().parse::().expect("Input must be a valid number!"); - devs.into_iter().enumerate() + let input = input + .trim() + .parse::() + .expect("Input must be a valid number!"); + devs.into_iter() + .enumerate() .filter(|(i, _)| *i == input as usize) .map(|(_i, (_name, dev))| dev) .take(1) @@ -104,13 +111,19 @@ fn select_input_device(devs: BTreeMap) -> Device { fn select_strategy() -> StrategyKind { println!("Available beat detection strategies:"); - StrategyKind::values().into_iter().enumerate().for_each(|(i, s)| { - println!(" [{}] {} - {}", i, s.name(), s.description()); - }); + StrategyKind::values() + .into_iter() + .enumerate() + .for_each(|(i, s)| { + println!(" [{}] {} - {}", i, s.name(), s.description()); + }); println!("Select strategy: input id and enter:"); let mut input = String::new(); while stdin().read_line(&mut input).unwrap() == 0 {} - let input = input.trim().parse::().expect("Input must be a valid number!"); + let input = input + .trim() + .parse::() + .expect("Input must be a valid number!"); match input { 0 => StrategyKind::LPF, 1 => StrategyKind::Spectrum, @@ -123,7 +136,10 @@ pub fn select_num_leds() -> u16 { println!("Input and enter how many LEDs are connected to your device (64, 150, ..):"); let mut input = String::new(); while stdin().read_line(&mut input).unwrap() == 0 {} - let input = input.trim().parse::().expect("Input must be a valid number!"); + let input = input + .trim() + .parse::() + .expect("Input must be a valid number!"); input } diff --git a/src/lib.rs b/src/lib.rs index d3deb73..3e4a346 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,24 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + +#![deny( +clippy::all, +clippy::cargo, +clippy::nursery, +// clippy::restriction, +// clippy::pedantic +)] +// now allow a few rules which are denied by the above statement +// --> they are ridiculous and not necessary +#![allow( + clippy::suboptimal_flops, + clippy::redundant_pub_crate, + clippy::fallible_impl_from +)] +#![deny(missing_debug_implementations)] +#![deny(rustdoc::all)] + use crate::strategies::lpf::LpfBeatDetector; use crate::strategies::spectrum::SABeatDetector; use crate::strategies::window_stats::WindowStats; @@ -37,12 +55,12 @@ pub struct BeatInfo { } impl BeatInfo { #[inline(always)] - pub fn new(relative_ms: u32) -> Self { + pub const fn new(relative_ms: u32) -> Self { Self { relative_ms } } #[inline(always)] - pub fn relative_ms(&self) -> u32 { + pub const fn relative_ms(&self) -> u32 { self.relative_ms } } @@ -138,7 +156,7 @@ impl StrategyKind { match self { StrategyKind::LPF => Box::new(LpfBeatDetector::new(sampling_rate)), StrategyKind::Spectrum => Box::new(SABeatDetector::new(sampling_rate)), - _ => panic!("Unknown Strategy"), + // _ => panic!("Unknown Strategy"), } } @@ -147,7 +165,7 @@ impl StrategyKind { match self { StrategyKind::LPF => LpfBeatDetector::name(), StrategyKind::Spectrum => SABeatDetector::name(), - _ => panic!("Unknown Strategy"), + // _ => panic!("Unknown Strategy"), } } @@ -156,12 +174,12 @@ impl StrategyKind { match self { StrategyKind::LPF => LpfBeatDetector::description(), StrategyKind::Spectrum => SABeatDetector::description(), - _ => panic!("Unknown Strategy"), + // _ => panic!("Unknown Strategy"), } } /// Returns a vector with all strategy kinds to iterate over them. - pub fn values() -> Vec { + pub fn values() -> Vec { vec![Self::LPF, Self::Spectrum] } } diff --git a/src/record.rs b/src/record.rs index 1a6250b..0688771 100644 --- a/src/record.rs +++ b/src/record.rs @@ -48,10 +48,10 @@ pub fn start_listening( // we either use the "cpal" audio device the user wants to use // or otherwise the default input device - let in_dev = input_dev.map(|d| Ok(d)).unwrap_or_else(|| { + let in_dev = input_dev.map(Ok).unwrap_or_else(|| { let host = cpal::default_host(); host.default_input_device() - .ok_or("Must have input device!".to_string()) + .ok_or_else(|| "Must have input device!".to_string()) })?; let in_dev_cfg = in_dev.default_input_config().unwrap(); // let channels = in_dev_cfg.channels(); @@ -67,6 +67,7 @@ pub fn start_listening( }; // 1/44100 * 1024 = 23.22ms + #[cfg(not(target_os = "linux"))] let preferred_window_length = 1024; let in_stream_cfg = StreamConfig { @@ -168,9 +169,8 @@ fn f32_data_to_i16(data: &[f32]) -> Vec { /// audio devices using "cpal" audio library. pub fn audio_input_device_list() -> BTreeMap { let host = cpal::default_host(); - let devs = host.input_devices().unwrap().collect::>(); let mut map = BTreeMap::new(); - for (i, dev) in devs.into_iter().enumerate() { + for (i, dev) in host.input_devices().unwrap().enumerate() { map.insert(dev.name().unwrap_or(format!("Unknown device #{}", i)), dev); } map @@ -180,8 +180,7 @@ pub fn audio_input_device_list() -> BTreeMap { /// each audio device covered by "cpal" audio library. pub fn print_audio_input_device_configs() { let host = cpal::default_host(); - let devs = host.input_devices().unwrap().collect::>(); - for (i, dev) in devs.into_iter().enumerate() { + for (i, dev) in host.input_devices().unwrap().enumerate() { eprintln!("--------"); let name = dev.name().unwrap_or(format!("Unknown device #{}", i)); eprintln!("[{}] default config:", name); diff --git a/src/strategies/mod.rs b/src/strategies/mod.rs index eaa221e..dafac54 100644 --- a/src/strategies/mod.rs +++ b/src/strategies/mod.rs @@ -92,7 +92,7 @@ impl AnalysisState { /// Getter for [`sampling_rate`]. #[inline(always)] - pub fn sampling_rate(&self) -> u32 { + pub const fn sampling_rate(&self) -> u32 { self.sampling_rate } diff --git a/src/strategies/window_stats.rs b/src/strategies/window_stats.rs index 6a7d2f7..8f2ba39 100644 --- a/src/strategies/window_stats.rs +++ b/src/strategies/window_stats.rs @@ -33,7 +33,7 @@ pub struct WindowStats { impl WindowStats { #[inline(always)] - pub fn max(&self) -> u16 { + pub const fn max(&self) -> u16 { self.max } } @@ -47,7 +47,7 @@ impl From<&[i16]> for WindowStats { .map(|x| if *x == i16::MIN { x + 1 } else { *x }) .map(|x| x.abs()) .collect::>(); - abs_samples_ordered.sort(); + abs_samples_ordered.sort_unstable(); let max = *abs_samples_ordered.last().unwrap() as u16; Self { max } From ff4bdc23e6ee8414ffd0adbe5bcb737c3eb8fc2c Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Sun, 5 Sep 2021 21:04:23 +0200 Subject: [PATCH 2/5] prepared v0.1.1 --- .travis.yml | 2 ++ Cargo.toml | 2 +- README.md | 3 +++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5f918f3..d8801a2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,5 +3,7 @@ before_install: # alsa header files, required for rust build - sudo apt update && sudo apt install libasound2-dev rust: + - 1.52.1 - stable + - nightly cache: cargo diff --git a/Cargo.toml b/Cargo.toml index 978d53d..a76c27e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ description = """ Audio beat detection library, that supports different audio input devices as source. You can pass a callback for each found beat to the library. """ -version = "0.1.0" +version = "0.1.1" authors = ["Philipp Schuster "] edition = "2018" license = "MIT" diff --git a/README.md b/README.md index c05fb57..f1bef0c 100644 --- a/README.md +++ b/README.md @@ -72,3 +72,6 @@ fn select_strategy() -> StrategyKind { StrategyKind::Spectrum } ``` + +## MSRV (Minimal Supported Rust Version) +1.52.1 stable From 58af1a829f3560d4495d414a142f0737ba221285 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Sun, 12 Sep 2021 13:17:03 +0200 Subject: [PATCH 3/5] use ringbuffer for spectrum strategy --- Cargo.toml | 1 + src/strategies/spectrum.rs | 50 ++++++++++++++++++++------------------ 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a76c27e..65b5c0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ documentation = "https://docs.rs/beat-detector" lowpass-filter = "0.2.4" spectrum-analyzer = "0.5.2" cpal = "0.13.3" +ringbuffer = "0.7.1" [dev-dependencies] minimp3 = "0.5.1" diff --git a/src/strategies/spectrum.rs b/src/strategies/spectrum.rs index c371a79..cc85c2d 100644 --- a/src/strategies/spectrum.rs +++ b/src/strategies/spectrum.rs @@ -25,6 +25,8 @@ use crate::strategies::window_stats::WindowStats; use crate::strategies::AnalysisState; use crate::{BeatInfo, Strategy, StrategyKind}; use spectrum_analyzer::FrequencyLimit; +use ringbuffer::{ConstGenericRingBuffer, RingBuffer, RingBufferWrite, RingBufferExt}; +use std::cell::RefCell; /// Struct to provide a beat-detection strategy using a /// Spectrum Analysis. The algorithm is pretty basic/stupid. @@ -34,60 +36,60 @@ use spectrum_analyzer::FrequencyLimit; #[derive(Debug)] pub struct SABeatDetector { state: AnalysisState, + // ring buffer with latest audio; necessary because we don't + // necessarily get 1024 samples at each callback but mostly + // 500-540.. + audio_data_buf: RefCell>, } impl SABeatDetector { #[inline(always)] pub fn new(sampling_rate: u32) -> Self { + const LEN: usize = 1024; + let mut initial_buf = ConstGenericRingBuffer::::new(); + (0..LEN).for_each(|_| initial_buf.push(0.0)); Self { state: AnalysisState::new(sampling_rate), + audio_data_buf: RefCell::from(initial_buf), } } - - /// Returns `frame_len` or the next power of 2. - #[inline(always)] - fn next_power_of_2(frame_len: usize) -> usize { - let exponent = (frame_len as f32).log2().ceil(); - 2_usize.pow(exponent as u32) - } } impl Strategy for SABeatDetector { - /// Analyzes if inside the window of samples a beat was found after - /// applying a lowpass filter onto the data. + /// Callback called when the audio input library got the next callback. #[inline(always)] - fn is_beat(&self, orig_samples: &[i16]) -> Option { - // make sure buffer has length that is a power of two for FFT - let len_power_of_2 = Self::next_power_of_2(orig_samples.len()); - let diff = len_power_of_2 - orig_samples.len(); - let mut samples = Vec::with_capacity(len_power_of_2); - samples.extend_from_slice(orig_samples); - samples.extend_from_slice(&vec![0; diff]); + fn is_beat(&self, callback_samples: &[i16]) -> Option { + // make sure we have the latest 1024 audio samples in the buffer + // => ready for FFT + let mut audio_data_buf = self.audio_data_buf.borrow_mut(); + for sample in callback_samples { + audio_data_buf.push(*sample as f32); + } // tell the state beforehand that we are analyzing the next window - important! - self.state.update_time(samples.len()); - // skip if distance to last beat is not fair away enough + self.state.update_time(callback_samples.len()); + // skip if distance to last beat is not far away enough if !self.last_beat_beyond_threshold(&self.state) { return None; }; // skip if the amplitude is too low, e.g. noise or silence between songs - let w_stats = WindowStats::from(samples.as_slice()); + let w_stats = WindowStats::from(callback_samples); if !self.amplitude_high_enough(&w_stats) { return None; }; - let samples = samples.iter().map(|x| *x as f32).collect::>(); - let spectrum = spectrum_analyzer::samples_fft_to_spectrum( - &samples, + &audio_data_buf.to_vec(), self.state.sampling_rate(), FrequencyLimit::Max(90.0), // scale values - Some(&|x| x / samples.len() as f32), + Some(&|x| x / audio_data_buf.len() as f32), None, ); - if spectrum.max().1.val() > 4400.0 { + // I don't know what the value really means :D + // figured out by testing + if spectrum.max().1.val() > 6200.0 { // mark we found a beat self.state.update_last_discovered_beat_timestamp(); Some(BeatInfo::new(self.state.beat_time_ms())) From c8f27a4cae76ea12ad75604a89974448d8f9047b Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Sun, 12 Sep 2021 13:47:55 +0200 Subject: [PATCH 4/5] updated to spectrum-analyzer 1.1.0 --- Cargo.toml | 2 +- src/strategies/spectrum.rs | 26 +++++++++----------------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 65b5c0a..89f5532 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ documentation = "https://docs.rs/beat-detector" [dependencies] lowpass-filter = "0.2.4" -spectrum-analyzer = "0.5.2" +spectrum-analyzer = "1.1.0" cpal = "0.13.3" ringbuffer = "0.7.1" diff --git a/src/strategies/spectrum.rs b/src/strategies/spectrum.rs index cc85c2d..7a41791 100644 --- a/src/strategies/spectrum.rs +++ b/src/strategies/spectrum.rs @@ -25,8 +25,9 @@ use crate::strategies::window_stats::WindowStats; use crate::strategies::AnalysisState; use crate::{BeatInfo, Strategy, StrategyKind}; use spectrum_analyzer::FrequencyLimit; -use ringbuffer::{ConstGenericRingBuffer, RingBuffer, RingBufferWrite, RingBufferExt}; +use ringbuffer::{ConstGenericRingBuffer, RingBufferWrite, RingBufferExt}; use std::cell::RefCell; +use spectrum_analyzer::scaling::divide_by_N; /// Struct to provide a beat-detection strategy using a /// Spectrum Analysis. The algorithm is pretty basic/stupid. @@ -82,14 +83,13 @@ impl Strategy for SABeatDetector { &audio_data_buf.to_vec(), self.state.sampling_rate(), FrequencyLimit::Max(90.0), - // scale values - Some(&|x| x / audio_data_buf.len() as f32), - None, - ); + // None, + Some(÷_by_N), + ).unwrap(); // I don't know what the value really means :D - // figured out by testing - if spectrum.max().1.val() > 6200.0 { + // figured out by testing.. :/ + if spectrum.max().1.val() > 2_100_000.0 { // mark we found a beat self.state.update_last_discovered_beat_timestamp(); Some(BeatInfo::new(self.state.beat_time_ms())) @@ -134,14 +134,6 @@ impl Strategy for SABeatDetector { #[cfg(test)] mod tests { - use super::*; + // use super::*; - #[test] - fn test_next_power_of_2() { - assert_eq!(2, SABeatDetector::next_power_of_2(2)); - assert_eq!(16, SABeatDetector::next_power_of_2(16)); - assert_eq!(128, SABeatDetector::next_power_of_2(127)); - assert_eq!(128, SABeatDetector::next_power_of_2(128)); - assert_eq!(256, SABeatDetector::next_power_of_2(129)); - } -} +} \ No newline at end of file From 6e6c043b8cd8b2a42a897e950d604e1687d25d6d Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Sun, 12 Sep 2021 14:46:47 +0200 Subject: [PATCH 5/5] v0.1.2 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 89f5532..13f8f20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ description = """ Audio beat detection library, that supports different audio input devices as source. You can pass a callback for each found beat to the library. """ -version = "0.1.1" +version = "0.1.2" authors = ["Philipp Schuster "] edition = "2018" license = "MIT"