1use std::cmp::Ordering;
9use std::sync::Arc;
10
11use crate::util;
12
13pub fn v2s_f32_rounded(digits: usize) -> Arc<dyn Fn(f32) -> String + Send + Sync> {
20 let rounding_multiplier = 10u32.pow(digits as u32) as f32;
21 Arc::new(move |value| {
22 if (value * rounding_multiplier).round() / rounding_multiplier == 0.0 {
24 format!("{:.digits$}", 0.0)
25 } else {
26 format!("{value:.digits$}")
27 }
28 })
29}
30
31pub fn v2s_f32_percentage(digits: usize) -> Arc<dyn Fn(f32) -> String + Send + Sync> {
34 Arc::new(move |value| format!("{:.digits$}", value * 100.0))
35}
36
37pub fn s2v_f32_percentage() -> Arc<dyn Fn(&str) -> Option<f32> + Send + Sync> {
40 Arc::new(|string| {
41 string
42 .trim_end_matches([' ', '%'])
43 .parse()
44 .ok()
45 .map(|x: f32| x / 100.0)
46 })
47}
48
49pub fn v2s_compression_ratio(digits: usize) -> Arc<dyn Fn(f32) -> String + Send + Sync> {
52 Arc::new(move |value| {
53 if value >= 1.0 {
54 format!("{value:.digits$}:1")
55 } else {
56 format!("1:{:.digits$}", value.recip())
57 }
58 })
59}
60
61pub fn s2v_compression_ratio() -> Arc<dyn Fn(&str) -> Option<f32> + Send + Sync> {
64 Arc::new(|string| {
65 let string = string.trim();
66 string
67 .trim()
68 .split_once(':')
69 .and_then(|(numerator, denominator)| {
70 let numerator: f32 = numerator.trim().parse().ok()?;
71 let denominator: f32 = denominator.trim().parse().ok()?;
72
73 Some(numerator / denominator)
74 })
75 .or_else(|| string.parse().ok())
77 })
78}
79
80pub fn v2s_f32_gain_to_db(digits: usize) -> Arc<dyn Fn(f32) -> String + Send + Sync> {
86 let rounding_multiplier = 10u32.pow(digits as u32) as f32;
87 Arc::new(move |value| {
88 if value < util::MINUS_INFINITY_GAIN {
89 String::from("-inf")
90 } else {
91 let value_db = util::gain_to_db(value);
92
93 if (value_db * rounding_multiplier).round() / rounding_multiplier == 0.0 {
95 format!("{:.digits$}", 0.0)
96 } else {
97 format!("{value_db:.digits$}")
98 }
99 }
100 })
101}
102
103pub fn s2v_f32_gain_to_db() -> Arc<dyn Fn(&str) -> Option<f32> + Send + Sync> {
106 Arc::new(|string| {
107 let string = string.trim_end_matches([' ', 'd', 'D', 'b', 'B', 'f', 'F', 's', 'S']);
108 if string.eq_ignore_ascii_case("-in") {
110 Some(0.0)
111 } else {
112 string.parse().ok().map(util::db_to_gain)
113 }
114 })
115}
116
117pub fn v2s_f32_panning() -> Arc<dyn Fn(f32) -> String + Send + Sync> {
120 Arc::new(move |value| match value.partial_cmp(&0.0) {
121 Some(Ordering::Less) => format!("{:.0}L", value * -100.0),
122 Some(Ordering::Equal) => String::from("C"),
123 Some(Ordering::Greater) => format!("{:.0}R", value * 100.0),
124 None => String::from("NaN"),
125 })
126}
127
128pub fn s2v_f32_panning() -> Arc<dyn Fn(&str) -> Option<f32> + Send + Sync> {
131 Arc::new(|string| {
132 let string = string.trim();
133 let cleaned_string = string
134 .trim_end_matches([' ', 'l', 'L', 'c', 'C', 'r', 'R'])
135 .parse()
136 .ok();
137 match string.chars().last()?.to_uppercase().next()? {
138 'L' => cleaned_string.map(|x: f32| x / -100.0),
139 'C' => Some(0.0),
140 'R' => cleaned_string.map(|x: f32| x / 100.0),
141 _ => None,
142 }
143 })
144}
145
146pub fn v2s_f32_hz_then_khz(digits: usize) -> Arc<dyn Fn(f32) -> String + Send + Sync> {
149 Arc::new(move |value| {
150 if value.round() < 1000.0 {
151 format!("{value:.digits$} Hz")
152 } else {
153 format!("{:.digits$} kHz", value / 1000.0, digits = digits.max(1))
154 }
155 })
156}
157
158#[deprecated(
161 since = "0.4.1",
162 note = "Use v2s_f32_hz_then_khz_with_key_name instead"
163)]
164pub fn v2s_f32_hz_then_khz_with_note_name(
165 digits: usize,
166 include_cents: bool,
167) -> Arc<dyn Fn(f32) -> String + Send + Sync> {
168 v2s_f32_hz_then_khz_with_key_name(digits, include_cents)
169}
170
171pub fn v2s_f32_hz_then_khz_with_key_name(
174 digits: usize,
175 include_cents: bool,
176) -> Arc<dyn Fn(f32) -> String + Send + Sync> {
177 Arc::new(move |value| {
178 if value.abs() < 1.0 {
181 return format!("{value:.digits$} Hz");
182 }
183
184 let fractional_key = util::freq_to_midi_key(value);
186 let key = fractional_key.round();
187 let cents = ((fractional_key - key) * 100.0).round() as i32;
188
189 let key_name = util::KEYS[(key as i32).rem_euclid(12) as usize];
190 let octave = (key / 12.0).floor() as i32 - 1;
193 let key_str = if cents == 0 || !include_cents {
194 format!("{key_name}{octave}")
195 } else {
196 format!("{key_name}{octave}, {cents:+} ct.")
197 };
198
199 if value < 1000.0 {
200 format!("{value:.digits$} Hz, {key_str}")
201 } else {
202 format!(
203 "{:.digits$} kHz, {}",
204 value / 1000.0,
205 key_str,
206 digits = digits.max(1)
207 )
208 }
209 })
210}
211
212pub fn s2v_f32_hz_then_khz() -> Arc<dyn Fn(&str) -> Option<f32> + Send + Sync> {
216 let key_formatter = s2v_i32_key_formatter();
219
220 Arc::new(move |string| {
221 let string = string.trim();
222
223 let mut segments = string.split(',');
230 let segments = (segments.next(), segments.next(), segments.next());
231
232 if let (_, Some(midi_key_number_str), Some(cents_str))
233 | (Some(midi_key_number_str), Some(cents_str), None) = segments
234 {
235 let cents_str = cents_str
236 .trim_start_matches([' ', '+'])
237 .trim_end_matches([' ', 'C', 'c', 'E', 'e', 'N', 'n', 'T', 't', 'S', 's', '.']);
238
239 if let (Some(midi_key_number), Ok(cents)) =
240 (key_formatter(midi_key_number_str), cents_str.parse::<i32>())
241 {
242 let plain_key_freq = util::f32_midi_key_to_freq(midi_key_number as f32);
243 let cents_multiplier = 2.0f32.powf(cents as f32 / 100.0 / 12.0);
244 return Some(plain_key_freq * cents_multiplier);
245 }
246 }
247
248 if let (_, Some(midi_key_number_str), _) | (Some(midi_key_number_str), None, None) =
249 segments
250 && let Some(midi_key_number) = key_formatter(midi_key_number_str)
251 {
252 return Some(util::f32_midi_key_to_freq(midi_key_number as f32));
253 }
254
255 let frequency_segment = segments.0?;
257 let cleaned_string = frequency_segment
258 .trim_end_matches([' ', 'k', 'K', 'h', 'H', 'z', 'Z'])
259 .parse()
260 .ok();
261 match frequency_segment.get(frequency_segment.len().saturating_sub(3)..) {
262 Some(unit) if unit.eq_ignore_ascii_case("khz") => cleaned_string.map(|x| x * 1000.0),
263 _ => cleaned_string,
265 }
266 })
267}
268
269pub fn v2s_i32_power_of_two() -> Arc<dyn Fn(i32) -> String + Send + Sync> {
272 Arc::new(|value| format!("{}", 1 << value))
273}
274
275pub fn s2v_i32_power_of_two() -> Arc<dyn Fn(&str) -> Option<i32> + Send + Sync> {
278 Arc::new(|string| string.parse().ok().map(|n: i32| (n as f32).log2() as i32))
279}
280
281#[deprecated(since = "0.4.1", note = "Use v2s_i32_key_formatter instead")]
284pub fn v2s_i32_note_formatter() -> Arc<dyn Fn(i32) -> String + Send + Sync> {
285 v2s_i32_key_formatter()
286}
287
288#[deprecated(since = "0.4.1", note = "Use s2v_i32_key_formatter instead")]
290pub fn s2v_i32_note_formatter() -> Arc<dyn Fn(&str) -> Option<i32> + Send + Sync> {
291 s2v_i32_key_formatter()
292}
293
294pub fn v2s_i32_key_formatter() -> Arc<dyn Fn(i32) -> String + Send + Sync> {
297 Arc::new(move |value| {
298 let key_name = util::KEYS[value.rem_euclid(12) as usize];
299 let octave = (value / 12) - 1;
300 format!("{key_name}{octave}")
301 })
302}
303
304pub fn s2v_i32_key_formatter() -> Arc<dyn Fn(&str) -> Option<i32> + Send + Sync> {
306 Arc::new(|string| {
307 let string = string.trim();
308 if string.len() < 2 {
309 return None;
310 }
311
312 let (key_name, octave) = string
316 .split_once(|c: char| c.is_whitespace())
317 .unwrap_or_else(|| {
318 if string.len() > 2 && &string[1..2] == "#" {
320 (&string[..2], &string[2..])
321 } else {
322 (&string[..1], &string[1..])
323 }
324 });
325
326 let key_number = util::KEYS
327 .iter()
328 .position(|&candidate| key_name.eq_ignore_ascii_case(candidate))?
329 as i32;
330 let octave: i32 = octave.trim().parse().ok()?;
331
332 Some(key_number + (12 * (octave + 1)))
334 })
335}
336
337pub fn v2s_bool_bypass() -> Arc<dyn Fn(bool) -> String + Send + Sync> {
340 Arc::new(move |value| {
341 if value {
342 String::from("Bypassed")
343 } else {
344 String::from("Not Bypassed")
345 }
346 })
347}
348
349pub fn s2v_bool_bypass() -> Arc<dyn Fn(&str) -> Option<bool> + Send + Sync> {
351 Arc::new(|string| {
352 let string = string.trim();
353 if string.eq_ignore_ascii_case("bypassed") {
354 Some(true)
355 } else if string.eq_ignore_ascii_case("not bypassed") {
356 Some(false)
357 } else {
358 None
359 }
360 })
361}
362
363#[cfg(test)]
364mod tests {
365 use super::*;
366
367 #[test]
369 fn v2s_f32_rounded_negative_zero() {
370 let v2s = v2s_f32_rounded(2);
371
372 assert_eq!("0.00", v2s(-0.001));
373
374 assert_eq!("-0.01", v2s(-0.009));
376 assert_eq!("0.01", v2s(0.009));
377 }
378
379 #[test]
382 fn f32_hz_then_khz_with_key_name_roundtrip() {
383 let v2s = v2s_f32_hz_then_khz_with_key_name(1, true);
384 let s2v = s2v_f32_hz_then_khz();
385
386 for freq in [0.0, 5.0, 7.18, 8.18, 69.420, 18181.8, 133333.7] {
387 let string = v2s(freq);
388 let roundtrip_freq = s2v(&string).unwrap();
391 let roundtrip_string = v2s(roundtrip_freq);
392 assert_eq!(
393 string, roundtrip_string,
394 "Unexpected: {string} -> {roundtrip_freq} -> {roundtrip_string}"
395 );
396 }
397 }
398}