rodio
rodio copied to clipboard
Playback too fast or too slow
I'm building a small toy project using a Pi Zero with a MAX98357 DAC configured with ALSA. Works fine, can stream audio with mpg123 without issues. The barebones example of rodio works aswell, but I'm having issues when I try to add my own code to it - audio is slowed down, if I build the app it is even worse - almost complete garbage comes out.
My code controls a HC-SR04, which I use to trigger different sounds. It has to put the main thread to sleep to function properly.
I've tried different formats (mp3/ogg), using Sink and play_raw, but the audio is slowed down or complete garbage, depending if I build for release or not.
Tried playing around with opt-level - no difference.
Also tried sleeping the main thread while the audio is playing.
Is this is Rust thing I don't understand? rodio plays the audio in a different thread, so messing with main should not be an issue. Is the Pi Zero just too weak for this? Perhaps it is an alsa configuration issue?
sonar:
use rppal::gpio::{Gpio, OutputPin, InputPin};
use std::time::{Duration, Instant};
use std::thread;
const GPIO_TRIG: u8 = 4;
const GPIO_ECHO: u8 = 24;
const SONIC_SPEED: f64 = 0.034; // cm/micro second
pub struct Sonar {
trig: OutputPin,
echo: InputPin,
}
impl Sonar {
pub fn new() -> Option<Sonar> {
let gpio = match Gpio::new() {
Ok(gpio) => gpio,
Err(e) => {
println!("{:?}", e);
return None;
}
};
let trig = match gpio.get(GPIO_TRIG) {
Ok(pin) => {
let output = pin.into_output();
output
}
Err(e) => {
println!("{:?}", e);
return None;
}
};
let echo = match gpio.get(GPIO_ECHO) {
Ok(pin) => {
let input = pin.into_input();
input
}
Err(e) => {
println!("{:?}", e);
return None;
}
};
Some(Sonar { echo, trig })
}
/// Returns a distance sample.
/// Will return -1 if something was a foot
pub fn get_distance(&mut self) -> f64 {
self.trig.set_low();
thread::sleep(Duration::from_micros(2));
self.trig.set_high();
thread::sleep(Duration::from_micros(10));
self.trig.set_low();
let mut init = Instant::now();
let mut start = Instant::now();
let mut duration = Duration::new(0, 0);
while self.echo.is_low() {
start = Instant::now();
if init.elapsed().as_millis() > 30 {
// println!("was never low");
return -1.0;
}
}
init = Instant::now();
while self.echo.is_high() {
duration = start.elapsed();
if init.elapsed().as_millis() > 30 {
// println!("was never high");
return -1.0;
}
}
let micros = duration.as_micros();
let distance = (SONIC_SPEED * micros as f64) / 2.0;
return distance;
}
}
sonar_state
use rppal::gpio::{Gpio, OutputPin};
use crate::sonar::Sonar;
const GPIO_LED: u8 = 23;
// Turn on distance
const TRIGGER_DISTANCE: f64 = 20.0;
// Turn off distance
const TRIGGER_END_DISTANCE: f64 = TRIGGER_DISTANCE * 2.0;
const DIST_SAMPLE_COUNT: isize = 5;
#[derive(Debug, PartialEq, Clone)]
pub enum StatusState {
OnTrigger,
OnTriggerEnd,
OffTrigger,
OffTriggerEnd,
}
pub struct SonarState {
sonar: Sonar,
led: OutputPin,
status_state: StatusState,
}
impl SonarState {
pub fn new() -> Option<SonarState> {
let sonar = match Sonar::new() {
Some(sonar) => sonar,
None => {
println!("Failed to create Sonar");
return None;
}
};
let gpio = match Gpio::new() {
Ok(gpio) => gpio,
Err(e) => {
println!("{:?}", e);
return None;
}
};
let mut status_led = match gpio.get(GPIO_LED) {
Ok(pin) => pin.into_output(),
Err(e) => {
println!("{:?}", e);
return None;
}
};
status_led.set_low();
Some(SonarState { sonar, led: status_led, status_state: StatusState::OnTrigger })
}
fn toggle_led(&mut self) {
self.led.toggle();
}
fn vector_median(&self, vector: &Vec<f64>) -> f64 {
if vector.len() % 2 != 0 {
vector[vector.len() / 2]
} else {
let lower = vector[(vector.len() as f64 / 2.0).floor() as usize];
let upper = vector[(vector.len() as f64 / 2.0).ceil() as usize];
(lower + upper) / 2.0
}
}
/**
Filtering our sample set, returning the median value.
*/
fn filter(&mut self, samples: &mut Vec<f64>) -> f64 {
samples.sort_by(|a, b| a.partial_cmp(b).unwrap());
let median = self.vector_median(&samples);
median
}
pub fn state_tick(&mut self) {
// Mutable, because we need to call functions on it
let mut samples: Vec<f64> = Vec::new();
for _ in 0..DIST_SAMPLE_COUNT {
let dist = self.sonar.get_distance();
if dist > 0.0 {
samples.push(dist);
}
}
if samples.len() == 0 {
return;
}
// Passing as a mutable reference to the function can change the variable
let distance = self.filter(&mut samples);
// Quick debug if necessary
// println!("d: {}, s: {:?}", distance, self.status_state);
match self.status_state {
StatusState::OnTrigger => {
if distance < TRIGGER_DISTANCE {
self.status_state = StatusState::OnTriggerEnd;
self.toggle_led();
}
}
StatusState::OnTriggerEnd => {
if distance > TRIGGER_END_DISTANCE {
self.status_state = StatusState::OffTrigger;
}
}
StatusState::OffTrigger => {
if distance < TRIGGER_DISTANCE {
self.status_state = StatusState::OffTriggerEnd;
self.toggle_led();
}
}
StatusState::OffTriggerEnd => {
if distance > TRIGGER_END_DISTANCE {
self.status_state = StatusState::OnTrigger;
}
}
}
}
/// Returning a clone as its a simple enum
pub fn get_state(&mut self) -> StatusState {
self.status_state.clone()
}
}
main:
mod sonar_state;
mod sonar;
use sonar_state::StatusState;
use std::thread;
use std::time::{Duration};
// use std::fs::File;
use std::io::BufReader;
// use rodio::Source;
fn main() {
let (_stream, stream_handle) = rodio::OutputStream::try_default().unwrap();
let sink = rodio::Sink::try_new(&stream_handle).unwrap();
// Sonar setup
let mut sonar_state = match sonar_state::SonarState::new()
{
Some(state) => state,
None => {
println!("Failed to create state");
return;
}
};
let mut last_state: StatusState = sonar_state.get_state();
loop {
sonar_state.state_tick();
let state: StatusState = sonar_state.get_state();
match state {
StatusState::OnTrigger => {
last_state = state;
let file = std::fs::File::open("audio/welcome.ogg").unwrap();
sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap());
sink.sleep_until_end();
}
StatusState::OnTriggerEnd => {
if state != last_state {
last_state = state;
let file = std::fs::File::open("audio/welcome.ogg").unwrap();
sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap());
sink.sleep_until_end();
}
}
StatusState::OffTrigger => {
last_state = state;
}
StatusState::OffTriggerEnd => {
if state != last_state {
last_state = state;
let file = std::fs::File::open("audio/bye.ogg").unwrap();
sink.append(rodio::Decoder::new(BufReader::new(file)).unwrap());
sink.sleep_until_end();
}
}
}
}
}```