ev3dev_lang_rust/sound.rs
1//! Sound-related functions. It can beep, play wav files, or convert text to
2//! speech.
3//!
4//! Note that all methods of the module spawn system processes and return
5//! `std::process::Child` objects. The methods are asynchronous (they return
6//! immediately after child process was spawned, without waiting for its
7//! completion), but you can call wait() on the returned result.
8//!
9//! # Examples
10//! ```no_run
11//! # use ev3dev_lang_rust::Ev3Result;
12//! use ev3dev_lang_rust::sound;
13//!
14//! # fn main() -> Ev3Result<()> {
15//! // Play "bark.wav", return immediately:
16//! sound::play("bark.wav")?;
17//!
18//! // Introduce yourself, wait for completion:
19//! sound::speak("Hello, I am Robot")?.wait()?;
20//! # Ok(())
21//! # }
22//! ```
23
24use crate::{Ev3Error, Ev3Result};
25use std::ffi::OsStr;
26use std::process::{Child, Command, Stdio};
27
28/// Call beep command.
29///
30/// # Example
31/// ```no_run
32/// # use ev3dev_lang_rust::Ev3Result;
33/// use ev3dev_lang_rust::sound;
34///
35/// # fn main() -> Ev3Result<()> {
36/// sound::beep()?.wait()?;
37/// # Ok(())
38/// # }
39/// ```
40pub fn beep() -> Ev3Result<Child> {
41 Ok(Command::new("/usr/bin/beep")
42 .stdout(Stdio::null())
43 .spawn()?)
44}
45
46/// Call beep command with the provided arguments.
47///
48/// See `beep man page`_ and google `linux beep music`_ for inspiration.
49/// * `beep man page`: <https://linux.die.net/man/1/beep>
50/// * `linux beep music`: <https://www.google.com/search?q=linux+beep+music>
51///
52/// # Example
53/// ```no_run
54/// # use ev3dev_lang_rust::Ev3Result;
55/// use ev3dev_lang_rust::sound;
56///
57/// # fn main() -> Ev3Result<()> {
58/// sound::beep_args(&[""])?.wait()?;
59/// # Ok(())
60/// # }
61/// ```
62pub fn beep_args<I, S>(args: I) -> Ev3Result<Child>
63where
64 I: IntoIterator<Item = S>,
65 S: AsRef<OsStr>,
66{
67 Ok(Command::new("/usr/bin/beep")
68 .args(args)
69 .stdout(Stdio::null())
70 .spawn()?)
71}
72
73/// Play tone sequence. The tone_sequence parameter is a list of tuples,
74/// where each tuple contains up to three numbers. The first number is
75/// frequency in Hz, the second is duration in milliseconds, and the third
76/// is delay in milliseconds between this and the next tone in the
77/// sequence.
78///
79/// # Example
80/// ```no_run
81/// # use ev3dev_lang_rust::Ev3Result;
82/// use ev3dev_lang_rust::sound;
83///
84/// # fn main() -> Ev3Result<()> {
85/// sound::tone(466.0, 500)?.wait()?;
86/// # Ok(())
87/// # }
88pub fn tone(frequency: f32, duration: i32) -> Ev3Result<Child> {
89 beep_args(vec![format!("-f {frequency}"), format!("-l {duration}")])
90}
91
92/// Play tone sequence. The tone_sequence parameter is a list of tuples,
93/// where each tuple contains up to three numbers. The first number is
94/// frequency in Hz, the second is duration in milliseconds, and the third
95/// is delay in milliseconds between this and the next tone in the
96/// sequence.
97///
98/// # Example
99/// ```no_run
100/// # use ev3dev_lang_rust::Ev3Result;
101/// use ev3dev_lang_rust::sound;
102///
103/// # fn main() -> Ev3Result<()> {
104/// sound::tone_sequence(
105/// &[
106/// (392.00, 350, 100), (392.00, 350, 100), (392.00, 350, 100), (311.1, 250, 100),
107/// (466.20, 025, 100), (392.00, 350, 100), (311.10, 250, 100), (466.2, 025, 100),
108/// (392.00, 700, 100), (587.32, 350, 100), (587.32, 350, 100),
109/// (587.32, 350, 100), (622.26, 250, 100), (466.20, 025, 100),
110/// (369.99, 350, 100), (311.10, 250, 100), (466.20, 025, 100), (392.00, 700, 100),
111/// (784.00, 350, 100), (392.00, 250, 100), (392.00, 025, 100), (784.00, 350, 100),
112/// (739.98, 250, 100), (698.46, 025, 100), (659.26, 025, 100),
113/// (622.26, 025, 100), (659.26, 050, 400), (415.30, 025, 200), (554.36, 350, 100),
114/// (523.25, 250, 100), (493.88, 025, 100), (466.16, 025, 100), (440.00, 025, 100),
115/// (466.16, 050, 400), (311.13, 025, 200), (369.99, 350, 100),
116/// (311.13, 250, 100), (392.00, 025, 100), (466.16, 350, 100), (392.00, 250, 100),
117/// (466.16, 025, 100), (587.32, 700, 100), (784.00, 350, 100), (392.00, 250, 100),
118/// (392.00, 025, 100), (784.00, 350, 100), (739.98, 250, 100), (698.46, 025, 100),
119/// (659.26, 025, 100), (622.26, 025, 100), (659.26, 050, 400), (415.30, 025, 200),
120/// (554.36, 350, 100), (523.25, 250, 100), (493.88, 025, 100),
121/// (466.16, 025, 100), (440.00, 025, 100), (466.16, 050, 400), (311.13, 025, 200),
122/// (392.00, 350, 100), (311.13, 250, 100), (466.16, 025, 100),
123/// (392.00, 300, 150), (311.13, 250, 100), (466.16, 025, 100), (392.00, 700, 0)
124/// ]
125/// )?.wait()?;
126/// # Ok(())
127/// # }
128pub fn tone_sequence(sequence: &[(f32, i32, i32)]) -> Ev3Result<Child> {
129 let tones: Vec<String> = sequence
130 .iter()
131 .map(|(frequency, duration, delay)| {
132 vec![
133 format!("-f {frequency}"),
134 format!("-l {duration}"),
135 format!("-D {delay}"),
136 ]
137 })
138 .collect::<Vec<Vec<String>>>()
139 .join(&["-n".to_owned()][..]);
140
141 beep_args(tones)
142}
143
144/// Play wav file
145pub fn play(wav_file: &str) -> Ev3Result<Child> {
146 Ok(Command::new("/usr/bin/aplay")
147 .arg("-q")
148 .arg("-Dplug:dmix")
149 .arg(wav_file)
150 .stdout(Stdio::null())
151 .spawn()?)
152}
153
154/// Speak the given text aloud.
155pub fn speak(text: &str) -> Ev3Result<Child> {
156 let espeak = Command::new("/usr/bin/espeak")
157 .args(["--stdout", "-a", "200", "-s", "130", text])
158 .stdout(Stdio::piped())
159 .spawn()?;
160
161 Ok(Command::new("/usr/bin/aplay")
162 .arg("-q")
163 .arg("-Dplug:dmix")
164 .stdin(espeak.stdout.ok_or(Ev3Error::InternalError {
165 msg: "`espeak` pipe to `aplay` could not be created!".to_owned(),
166 })?)
167 .stdout(Stdio::null())
168 .spawn()?)
169}
170
171/// Get the main channel name or 'Playback' if not available.
172fn get_channels() -> Ev3Result<Vec<String>> {
173 let out = String::from_utf8(
174 Command::new("/usr/bin/amixer")
175 .arg("scontrols")
176 .output()?
177 .stdout,
178 )?;
179
180 let mut channels: Vec<String> = out
181 .split('\n')
182 .filter_map(|line| {
183 let vol_start = line.find('\'').unwrap_or(0) + 1;
184 let vol_end = line.rfind('\'').unwrap_or(1);
185
186 if vol_start >= vol_end {
187 None
188 } else {
189 Some(line[vol_start..vol_end].to_owned())
190 }
191 })
192 .collect();
193
194 if channels.is_empty() {
195 channels.push("Playback".to_owned());
196 }
197
198 Ok(channels)
199}
200
201/// Sets the sound volume to the given percentage [0-100] by calling
202/// `amixer -q set <channel> <pct>%`.
203pub fn set_volume_channel(volume: i32, channel: &str) -> Ev3Result<()> {
204 Command::new("/usr/bin/amixer")
205 .args(["-q", "set", channel, &format!("{volume}%")])
206 .stdout(Stdio::null())
207 .spawn()?
208 .wait()?;
209
210 Ok(())
211}
212
213/// Sets the sound volume to the given percentage [0-100] by calling
214/// `amixer -q set <channel> <pct>%`.
215/// It tries to determine the default channel
216/// by running `amixer scontrols`. If that fails as well, it uses the
217/// `Playback` channel, as that is the only channel on the EV3.
218pub fn set_volume(volume: i32) -> Ev3Result<()> {
219 for channel in get_channels()? {
220 set_volume_channel(volume, &channel)?;
221 }
222 Ok(())
223}
224
225/// Gets the current sound volume by parsing the output of
226/// `amixer get <channel>`.
227pub fn get_volume_channel(channel: &str) -> Ev3Result<i32> {
228 let out = String::from_utf8(
229 Command::new("/usr/bin/amixer")
230 .args(["get", channel])
231 .output()?
232 .stdout,
233 )?;
234
235 let vol_start = out.find('[').unwrap_or(0) + 1;
236 let vol_end = out.find("%]").unwrap_or(1);
237 let vol = &out[vol_start..vol_end].parse::<i32>()?;
238
239 Ok(*vol)
240}
241
242/// Gets the current sound volume by parsing the output of
243/// `amixer get <channel>`.
244/// It tries to determine the default channel
245/// by running `amixer scontrols`. If that fails as well, it uses the
246/// `Playback` channel, as that is the only channel on the EV3.
247pub fn get_volume() -> Ev3Result<i32> {
248 get_volume_channel(&get_channels()?[0])
249}