audio_processor_standalone/
options.rs1use std::ffi::OsString;
24
25use clap::ArgMatches;
26
27pub enum RenderingOptions {
28 Online {
29 input_file: Option<String>,
30 input_device: Option<String>,
31 output_device: Option<String>,
32 },
33 Offline {
34 input_file: String,
35 output_file: String,
36 },
37}
38
39impl RenderingOptions {
40 pub fn input_device(&self) -> Option<String> {
41 if let RenderingOptions::Online { input_device, .. } = &self {
42 input_device.clone()
43 } else {
44 None
45 }
46 }
47
48 pub fn output_device(&self) -> Option<String> {
49 if let RenderingOptions::Online { output_device, .. } = &self {
50 output_device.clone()
51 } else {
52 None
53 }
54 }
55}
56
57pub struct MidiOptions {
58 pub input_file: Option<String>,
59}
60
61pub struct Options {
62 midi: MidiOptions,
63 rendering: RenderingOptions,
64 list_hosts: bool,
65 list_input_devices: bool,
66 list_output_devices: bool,
67}
68
69impl Options {
70 pub fn rendering(&self) -> &RenderingOptions {
71 &self.rendering
72 }
73
74 pub fn midi(&self) -> &MidiOptions {
75 &self.midi
76 }
77
78 pub fn list_hosts(&self) -> bool {
79 self.list_hosts
80 }
81
82 pub fn list_input_devices(&self) -> bool {
83 self.list_input_devices
84 }
85
86 pub fn list_output_devices(&self) -> bool {
87 self.list_output_devices
88 }
89}
90
91pub struct ParseOptionsParams {
92 pub supports_midi: bool,
93}
94
95pub fn parse_options(params: ParseOptionsParams) -> Options {
96 parse_options_from(params, std::env::args_os())
97}
98
99fn parse_options_from<I, T>(params: ParseOptionsParams, args: I) -> Options
100where
101 I: IntoIterator<Item = T>,
102 T: Into<OsString> + Clone,
103{
104 let supports_midi = params.supports_midi;
105
106 let app = clap::App::new("audio-processor-standalone");
107 let mut app = app
108 .arg(clap::Arg::from_usage(
109 "-i, --input-file=[INPUT_PATH] 'An input audio file to process'",
110 ))
111 .arg(clap::Arg::from_usage(
112 "-o, --output-file=[OUTPUT_PATH] 'If specified, will render offline into this file (WAV)'",
113 ))
114 .arg(clap::Arg::from_usage(
115 "--input-device=[INPUT_DEVICE] 'The input device to use'",
116 ))
117 .arg(clap::Arg::from_usage(
118 "--output-device=[OUTPUT_DEVICE] 'The output device to use'",
119 ))
120 .arg(clap::Arg::from_usage(
121 "--list-hosts 'List the audio hosts available'",
122 ))
123 .arg(clap::Arg::from_usage(
124 "--list-input-devices 'List the input devices available'",
125 ))
126 .arg(clap::Arg::from_usage(
127 "--list-output-devices 'List the output devices available'",
128 ));
129
130 if supports_midi {
131 app = app
132 .arg(clap::Arg::from_usage(
133 "--midi-input-file=[MIDI_INPUT_FILE] 'If specified, this MIDI file will be passed through the processor'",
134 ));
135 }
136
137 let matches = app.get_matches_from(args);
138
139 let midi_options = parse_midi_options(&matches);
140 let rendering = parse_rendering_options(&matches);
141
142 Options {
143 midi: midi_options,
144 rendering,
145 list_hosts: matches.is_present("list-hosts"),
146 list_input_devices: matches.is_present("list-input-devices"),
147 list_output_devices: matches.is_present("list-output-devices"),
148 }
149}
150
151fn parse_midi_options(matches: &ArgMatches) -> MidiOptions {
152 MidiOptions {
153 input_file: matches.value_of("midi-input-file").map(|s| s.into()),
154 }
155}
156
157fn parse_rendering_options(matches: &ArgMatches) -> RenderingOptions {
158 if matches.is_present("output-file") {
159 if !matches.is_present("input-file") {
160 log::error!("Please specify `--input-file`");
161 std::process::exit(1);
162 }
163
164 let input_path = matches.value_of("input-file").map(|s| s.into()).unwrap();
165 let output_path = matches.value_of("output-file").map(|s| s.into()).unwrap();
166
167 RenderingOptions::Offline {
168 input_file: input_path,
169 output_file: output_path,
170 }
171 } else {
172 RenderingOptions::Online {
173 input_file: matches.value_of("input-file").map(|s| s.into()),
174 input_device: matches.value_of("input-device").map(|s| s.into()),
175 output_device: matches.value_of("output-device").map(|s| s.into()),
176 }
177 }
178}
179
180#[cfg(test)]
181mod test {
182 use super::*;
183
184 #[test]
185 fn test_parse_empty_options() {
186 let options = parse_options_from::<Vec<String>, String>(
187 ParseOptionsParams {
188 supports_midi: false,
189 },
190 vec![],
191 );
192 assert!(options.midi().input_file.is_none());
193 assert!(matches!(
194 options.rendering(),
195 RenderingOptions::Online { .. }
196 ));
197 }
198
199 #[test]
200 fn test_parse_list_hosts() {
201 let options = parse_options_from::<Vec<String>, String>(
202 ParseOptionsParams {
203 supports_midi: false,
204 },
205 vec!["program".into(), "--list-hosts".into()],
206 );
207 assert!(options.list_hosts());
208 }
209
210 #[test]
211 fn test_parse_list_input_devices() {
212 let options = parse_options_from::<Vec<String>, String>(
213 ParseOptionsParams {
214 supports_midi: false,
215 },
216 vec!["program".into(), "--list-input-devices".into()],
217 );
218 assert!(options.list_input_devices());
219 }
220
221 #[test]
222 fn test_parse_list_output_devices() {
223 let options = parse_options_from::<Vec<String>, String>(
224 ParseOptionsParams {
225 supports_midi: false,
226 },
227 vec!["program".into(), "--list-output-devices".into()],
228 );
229 assert!(options.list_output_devices());
230 }
231
232 #[test]
233 fn test_parse_online_options() {
234 let options = parse_options_from::<Vec<String>, String>(
235 ParseOptionsParams {
236 supports_midi: false,
237 },
238 vec![
239 "program".into(),
240 "--input-file".into(),
241 "test.mp3".into(),
242 "--input-device".into(),
243 "Microphone".into(),
244 "--output-device".into(),
245 "Default".into(),
246 ],
247 );
248 assert!(options.midi().input_file.is_none());
249 assert!(matches!(
250 options.rendering(),
251 RenderingOptions::Online { .. }
252 ));
253 match options.rendering() {
254 RenderingOptions::Online {
255 input_file,
256 input_device,
257 output_device,
258 } => {
259 assert_eq!(input_file.as_ref().unwrap(), "test.mp3");
260 assert_eq!(input_device.as_ref().unwrap(), "Microphone");
261 assert_eq!(output_device.as_ref().unwrap(), "Default");
262 }
263 _ => {}
264 }
265 }
266
267 #[test]
268 fn test_parse_midi_options() {
269 let options = parse_options_from::<Vec<String>, String>(
270 ParseOptionsParams {
271 supports_midi: true,
272 },
273 vec![
274 "program".into(),
275 "--midi-input-file".into(),
276 "bach.mid".into(),
277 ],
278 );
279 assert!(options.midi().input_file.is_some());
280 assert_eq!(options.midi().input_file.as_ref().unwrap(), "bach.mid")
281 }
282
283 #[test]
284 fn test_parse_offline_options() {
285 let options = parse_options_from::<Vec<String>, String>(
286 ParseOptionsParams {
287 supports_midi: false,
288 },
289 vec![
290 "program".into(),
291 "--input-file".into(),
292 "test.mp3".into(),
293 "--output-file".into(),
294 "test.wav".into(),
295 ],
296 );
297 assert!(options.midi().input_file.is_none());
298 assert!(matches!(
299 options.rendering(),
300 RenderingOptions::Offline { .. }
301 ));
302 match options.rendering() {
303 RenderingOptions::Offline {
304 input_file,
305 output_file,
306 } => {
307 assert_eq!(input_file, "test.mp3");
308 assert_eq!(output_file, "test.wav");
309 }
310 _ => {}
311 }
312 }
313}