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}