Pi Zero ALSA Rodio sound issues

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?

soner.rs:

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.rs:

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.rs:

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();
                }   
            }
        }
    }
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.