audio_processor_testing_helpers/
charts.rs1use std::time::Duration;
27
28use crate::generators::sine_buffer;
29use crate::util::rms_level;
30use audio_processor_traits::{AudioBuffer, AudioContext, AudioProcessor, AudioProcessorSettings};
31pub use plotters::prelude::*;
32
33struct FrequencyResponseResult {
34 frequency: f32,
35 relative_output_level: f32,
36}
37
38fn test_frequency_response<Processor>(
39 sample_rate: f32,
40 frequency: f32,
41 audio_processor: &mut Processor,
42) -> FrequencyResponseResult
43where
44 Processor: AudioProcessor<SampleType = f32>,
45{
46 let input_buffer = sine_buffer(sample_rate, frequency, Duration::from_millis(50));
47 let mut input_buffer = AudioBuffer::from_interleaved(1, &input_buffer);
48
49 let input_rms = rms_level(input_buffer.channel(0));
50 let mut context = AudioContext::from(AudioProcessorSettings::new(sample_rate, 1, 1, 512));
51 audio_processor.process(&mut context, &mut input_buffer);
52 let output_rms = rms_level(input_buffer.channel(0));
53
54 let relative_output_level = output_rms / input_rms;
55
56 FrequencyResponseResult {
57 frequency,
58 relative_output_level,
59 }
60}
61
62fn get_test_frequencies() -> Vec<f32> {
63 let mut frequencies = vec![];
64 let mut start_frequency = 20.0;
65 for _ in 0..200 {
66 frequencies.push(start_frequency);
67 start_frequency += 20.0;
68 }
69 frequencies
70}
71
72struct FrequencyResponseChartModel {
73 x_range: (f64, f64),
74 y_range: (f64, f64),
75 values: Vec<(f64, f64)>,
76}
77
78fn build_frequency_response_chart_model(
79 responses: Vec<FrequencyResponseResult>,
80) -> FrequencyResponseChartModel {
81 let min_x = 0.0;
82 let max_x = responses
83 .iter()
84 .map(|r| r.frequency as f64)
85 .fold(-1. / 0., f64::max);
86 let min_y = 0.0;
87 let max_y = responses
88 .iter()
89 .map(|r| r.relative_output_level as f64)
90 .fold(-1. / 0., f64::max);
91
92 let values = responses
93 .iter()
94 .map(|r| (r.frequency as f64, r.relative_output_level as f64))
95 .collect();
96
97 FrequencyResponseChartModel {
98 x_range: (min_x, max_x),
99 y_range: (min_y, max_y),
100 values,
101 }
102}
103
104pub fn generate_frequency_response_plot<Processor>(
106 filename: &str,
107 plot_name: &str,
108 audio_processor: &mut Processor,
109) where
110 Processor: AudioProcessor<SampleType = f32>,
111{
112 let mut settings = AudioProcessorSettings::default();
113 settings.sample_rate = 22050.0;
114 settings.input_channels = 1;
115 settings.output_channels = 1;
116 let mut context = AudioContext::from(settings);
117 audio_processor.prepare(&mut context);
118 let sample_rate = settings.sample_rate;
119
120 let frequencies = get_test_frequencies();
121 let responses = frequencies
122 .iter()
123 .map(|frequency| test_frequency_response(sample_rate, *frequency, audio_processor))
124 .collect();
125 let chart_model = build_frequency_response_chart_model(responses);
126
127 let filename = std::path::Path::new(filename);
128 let chart_filename = filename.with_file_name(format!(
129 "{}--{}.svg",
130 filename.file_name().unwrap().to_str().unwrap(),
131 plot_name
132 ));
133
134 let svg_backend = SVGBackend::new(&chart_filename, (1000, 1000));
135 let drawing_area = svg_backend.into_drawing_area();
136 drawing_area.fill(&WHITE).unwrap();
137
138 let mut chart = ChartBuilder::on(&drawing_area)
139 .caption(plot_name, ("sans-serif", 20))
140 .set_label_area_size(LabelAreaPosition::Left, 40)
141 .set_label_area_size(LabelAreaPosition::Bottom, 40)
142 .build_cartesian_2d(
143 chart_model.x_range.0..chart_model.x_range.1,
144 chart_model.y_range.0..chart_model.y_range.1,
145 )
146 .unwrap();
147
148 chart.configure_mesh().draw().unwrap();
149
150 chart
151 .draw_series(LineSeries::new(chart_model.values, &RED))
152 .unwrap();
153 println!(">>> Wrote {} chart to {:?}", plot_name, chart_filename);
154}
155
156type SeriesDef = (RGBColor, Vec<f32>);
157type MultiSeries = Vec<SeriesDef>;
158
159pub fn draw_vec_chart(filename: &str, plot_name: &str, vec: Vec<f32>) {
161 draw_multi_vec_charts(filename, plot_name, vec![(RED, vec)])
162}
163
164pub fn draw_multi_vec_charts(filename: &str, plot_name: &str, vecs: MultiSeries) {
166 let (_, vec) = &vecs[0];
167 let filename = std::path::Path::new(filename);
168 let chart_filename = filename.with_file_name(format!(
169 "{}--{}.png",
170 filename.file_name().unwrap().to_str().unwrap(),
171 plot_name
172 ));
173
174 let backend = BitMapBackend::new(&chart_filename, (1000, 200));
175 let drawing_area = backend.into_drawing_area();
176 drawing_area.fill(&WHITE).unwrap();
177
178 let x_range = (0, vec.len());
179 let y_range = (
180 vec.iter().cloned().fold(-1. / 0., f32::max) as f64,
181 vec.iter().cloned().fold(1. / 0., f32::min) as f64,
182 );
183
184 let mut chart = ChartBuilder::on(&drawing_area)
185 .caption(plot_name, ("sans-serif", 20))
186 .set_label_area_size(LabelAreaPosition::Left, 40)
187 .set_label_area_size(LabelAreaPosition::Bottom, 40)
188 .build_cartesian_2d(x_range.0..x_range.1, y_range.1..y_range.0)
189 .unwrap();
190
191 chart.configure_mesh().draw().unwrap();
192
193 for (color, vec) in vecs {
194 let values: Vec<(usize, f64)> = vec
195 .iter()
196 .enumerate()
197 .map(|(i, s)| (i, *s as f64))
198 .collect();
199 chart.draw_series(LineSeries::new(values, color)).unwrap();
200 }
201}
202
203#[cfg(test)]
204mod test {
205 #[test]
206 fn it_compiles() {}
207}