Skip to main content

use_feedback/
lib.rs

1#![forbid(unsafe_code)]
2//! Primitive feedback loop helpers.
3//!
4//! The crate intentionally keeps feedback logic explicit: a gain plus a simple
5//! positive or negative direction.
6//!
7//! # Examples
8//!
9//! ```rust
10//! use use_feedback::{negative_feedback, FeedbackDirection, FeedbackLoop};
11//!
12//! let loop_gain = FeedbackLoop::new(0.5, FeedbackDirection::Negative).unwrap();
13//! assert_eq!(loop_gain.apply(10.0, 2.0), 9.0);
14//! assert_eq!(negative_feedback(10.0, 2.0, 0.5).unwrap(), 9.0);
15//! ```
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum FeedbackDirection {
19    Positive,
20    Negative,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq)]
24pub struct FeedbackLoop {
25    pub gain: f64,
26    pub direction: FeedbackDirection,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum FeedbackError {
31    InvalidGain,
32}
33
34impl FeedbackLoop {
35    pub fn new(gain: f64, direction: FeedbackDirection) -> Result<Self, FeedbackError> {
36        if !gain.is_finite() {
37            return Err(FeedbackError::InvalidGain);
38        }
39
40        Ok(Self { gain, direction })
41    }
42
43    pub fn apply(&self, input: f64, feedback: f64) -> f64 {
44        match self.direction {
45            FeedbackDirection::Positive => input + self.gain * feedback,
46            FeedbackDirection::Negative => input - self.gain * feedback,
47        }
48    }
49}
50
51pub fn negative_feedback(input: f64, feedback: f64, gain: f64) -> Result<f64, FeedbackError> {
52    Ok(FeedbackLoop::new(gain, FeedbackDirection::Negative)?.apply(input, feedback))
53}
54
55pub fn positive_feedback(input: f64, feedback: f64, gain: f64) -> Result<f64, FeedbackError> {
56    Ok(FeedbackLoop::new(gain, FeedbackDirection::Positive)?.apply(input, feedback))
57}
58
59#[cfg(test)]
60mod tests {
61    use super::{
62        FeedbackDirection, FeedbackError, FeedbackLoop, negative_feedback, positive_feedback,
63    };
64
65    #[test]
66    fn applies_negative_feedback() {
67        let loop_gain = FeedbackLoop::new(0.5, FeedbackDirection::Negative).unwrap();
68
69        assert_eq!(loop_gain.apply(10.0, 2.0), 9.0);
70        assert_eq!(negative_feedback(10.0, 2.0, 0.5).unwrap(), 9.0);
71    }
72
73    #[test]
74    fn applies_positive_feedback() {
75        let loop_gain = FeedbackLoop::new(0.5, FeedbackDirection::Positive).unwrap();
76
77        assert_eq!(loop_gain.apply(10.0, 2.0), 11.0);
78        assert_eq!(positive_feedback(10.0, 2.0, 0.5).unwrap(), 11.0);
79    }
80
81    #[test]
82    fn rejects_non_finite_gain() {
83        assert_eq!(
84            FeedbackLoop::new(f64::NAN, FeedbackDirection::Negative),
85            Err(FeedbackError::InvalidGain)
86        );
87        assert_eq!(
88            positive_feedback(10.0, 2.0, f64::INFINITY),
89            Err(FeedbackError::InvalidGain)
90        );
91    }
92}