pm_remez/requirements.rs
1use crate::{
2 error::{Error, Result},
3 types::{Band, PMParameters, ParametersBuilder},
4};
5use num_traits::Float;
6
7/// Creates parameters for the Parks-McClellan algorithm in terms of a list of
8/// [`BandSetting`]s.
9///
10/// This function is used by one of the two coding styles supported by this
11/// crate. It uses a list of `BandSetting`s to specify each of the bands and
12/// its desired function and weight used in the Parks-McClellan algorithm. The
13/// `num_taps` parameter indicates the number of taps of FIR filter to be
14/// designed.
15///
16/// As in [`PMParameters`], some of the parameters required for the
17/// Parks-McClellan algorith are given default values (the same defaults as in
18/// `PMParameters`, and in particular, an even symmetry for the FIR taps). The
19/// defaults can be changed by using the methods defined by the
20/// [`ParametersBuilder`] trait, which is implemented by the object returned by
21/// this function.
22///
23/// In technical terms, this function constructs and returns an appropriate
24/// `PMParameters` object. However, the type parameters `D` and `W` of that
25/// object cannot be named, because they are closures with anonymous
26/// types. Therefore, an `impl ParametersBuilder` is used as the return type of
27/// the function.
28pub fn pm_parameters<T: Float>(
29 num_taps: usize,
30 band_settings: &[BandSetting<T>],
31) -> Result<impl ParametersBuilder<T> + '_> {
32 if band_settings.is_empty() {
33 return Err(Error::BandsEmpty);
34 }
35 let bands = band_settings.iter().map(|setting| setting.band()).collect();
36 let desired = move |f| desired_response(band_settings, f);
37 let weights = move |f| weight(band_settings, f);
38 PMParameters::new(num_taps, bands, desired, weights)
39}
40
41fn desired_response<T: Float>(settings: &[BandSetting<T>], freq: T) -> T {
42 let setting = closest_setting(settings, freq);
43 setting.desired_response.evaluate(freq, &setting.band)
44}
45
46fn weight<T: Float>(settings: &[BandSetting<T>], freq: T) -> T {
47 let setting = closest_setting(settings, freq);
48 setting.weight.evaluate(freq, &setting.band)
49}
50
51fn closest_setting<T: Float>(settings: &[BandSetting<T>], freq: T) -> &BandSetting<T> {
52 settings
53 .iter()
54 .min_by(|a, b| {
55 a.band
56 .distance(freq)
57 .partial_cmp(&b.band.distance(freq))
58 .unwrap()
59 })
60 .unwrap()
61}
62
63/// Band with desired response and weight [`Setting`]s.
64///
65/// This struct defines a band (a closed subinterval of [0.0, 0.5] in which the
66/// Parks-McClellan algorithm tries to minimize the maximum weighted error) to
67/// which a desired response and a weight function in the form of [`Setting`]s
68/// are attached. The struct is used in one of the coding styles supported by
69/// this crate. In such coding style, the Parks-McClellan algorithm parameters
70/// are defined in terms of a list of `BandSetting`s by calling the
71/// [`pm_parameters`] function.
72#[derive(Debug)]
73pub struct BandSetting<T> {
74 band: Band<T>,
75 desired_response: Setting<T>,
76 weight: Setting<T>,
77}
78
79impl<T: Float> BandSetting<T> {
80 /// Creates a new `BandSetting` with default weight.
81 ///
82 /// The `band_begin` and `band_end` parameter indicate the being and the end
83 /// of the band respectively. The `desired_response` parameter gives the
84 /// desired response in this band. The weight when using this constructor is
85 /// set to `constant(T::one())`. A custom weight can be defined using the
86 /// constructor [`BandSetting::with_weight`] instead, or by calling
87 /// [`BandSetting::set_weight`].
88 pub fn new(band_begin: T, band_end: T, desired_response: Setting<T>) -> Result<BandSetting<T>> {
89 let weight = constant(T::one());
90 BandSetting::with_weight(band_begin, band_end, desired_response, weight)
91 }
92
93 /// Creates a new `BandSetting` with a custom weight.
94 ///
95 /// The `weight` parameter gives the weight function in this band. The
96 /// remaining parameters behave as in [`BandSetting::new`].
97 pub fn with_weight(
98 band_begin: T,
99 band_end: T,
100 desired_response: Setting<T>,
101 weight: Setting<T>,
102 ) -> Result<BandSetting<T>> {
103 let band = Band::new(band_begin, band_end)?;
104 Ok(BandSetting {
105 band,
106 desired_response,
107 weight,
108 })
109 }
110
111 /// Returns the [`Band`] associated to this [`BandSetting`].
112 pub fn band(&self) -> Band<T> {
113 self.band
114 }
115
116 /// Sets the [`Band`] associated to this [`BandSetting`].
117 pub fn set_band(&mut self, band: Band<T>) {
118 self.band = band;
119 }
120
121 /// Sets the desired response used by this [`BandSetting`].
122 pub fn set_desired_response(&mut self, desired_response: Setting<T>) {
123 self.desired_response = desired_response;
124 }
125
126 /// Sets the weight function used by this [`BandSetting`].
127 pub fn set_weight(&mut self, weight: Setting<T>) {
128 self.weight = weight
129 }
130}
131
132/// Desired response or weight setting.
133///
134/// This struct is used to indicate the desired response or the weigth function
135/// for a band through a [`BandSetting`] object when using the coding style that
136/// employs the [`pm_parameters`] function to indicate the Parks-McClellan
137/// algorithm parameters in terms of a list of [`BandSetting`]s.
138///
139/// Values of this object are constructed using the [`constant`], [`linear`],
140/// and [`function`] functions, which create a [`Setting`] that represents a
141/// constant function, a linear function, or a function defined by an arbitrary
142/// closure respectively.
143#[derive(Debug)]
144pub struct Setting<T>(SettingData<T>);
145
146impl<T: Float> Setting<T> {
147 fn evaluate(&self, x: T, band: &Band<T>) -> T {
148 match &self.0 {
149 SettingData::Constant { value } => *value,
150 SettingData::Linear { begin, end } => {
151 let u = (x - band.begin()) / (band.end() - band.begin());
152 *begin + u * (*end - *begin)
153 }
154 SettingData::Function { f } => (f)(x),
155 }
156 }
157}
158
159enum SettingData<T> {
160 Constant { value: T },
161 Linear { begin: T, end: T },
162 Function { f: Box<dyn Fn(T) -> T> },
163}
164
165/// Creates a [`Setting`] that represents a constant function.
166pub fn constant<T: Float>(value: T) -> Setting<T> {
167 Setting(SettingData::Constant { value })
168}
169
170/// Creates a [`Setting`] that represents a linear function.
171///
172/// The function has the values `begin` and `end` at the begin and end of the
173/// band to which the `Setting` is applied respectively, and it is linearly
174/// interpolated for the remaining points of the band.
175pub fn linear<T: Float>(begin: T, end: T) -> Setting<T> {
176 Setting(SettingData::Linear { begin, end })
177}
178
179/// Creates a [`Setting`] that represents an arbitrary function.
180///
181/// The arbitrary function is provided as a boxed closure trait object. The
182/// closure will only be evaluated at points belonging to the band to which the
183/// `Setting` is applied.
184pub fn function<T: Float>(f: Box<dyn Fn(T) -> T>) -> Setting<T> {
185 Setting(SettingData::Function { f })
186}
187
188impl<T: std::fmt::Debug> std::fmt::Debug for SettingData<T> {
189 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190 match &self {
191 SettingData::Constant { value } => {
192 f.debug_struct("Constant").field("value", value).finish()
193 }
194 SettingData::Linear { begin, end } => f
195 .debug_struct("Linear")
196 .field("begin", begin)
197 .field("end", end)
198 .finish(),
199 SettingData::Function { .. } => f.debug_struct("Function").finish(),
200 }
201 }
202}