Rust Audio

Reusing buffers with Rodio

I’m attempting to build a simple OSC-controlled sampler application. To begin with I think I’d like to load the samples from disk into memory and then reuse them each time I receive an event to play them. A simplified example the reproduces the problem I’m seeing is:

extern crate rodio;

use std::fs::File;
use std::io::BufReader;
use std::{thread, time};

fn main() {
    let device = rodio::default_output_device().unwrap();
    let file = File::open("1.wav").unwrap();
    let buffer = BufReader::new(file);

    rodio::play_once(&device, buffer).unwrap().detach();

    thread::sleep(time::Duration::from_millis(500));

    rodio::play_once(&device, buffer).unwrap().detach();

    thread::sleep(time::Duration::from_millis(500));
}

I get an error when compiling this example:

error[E0382]: use of moved value: `buffer`
  --> src/main.rs:16:31
   |
10 |     let buffer = BufReader::new(file);
   |         ------ move occurs because `buffer` has type `std::io::BufReader<std::fs::File>`, which does not implement the `Copy` trait
11 |
12 |     rodio::play_once(&device, buffer).unwrap();
   |                               ------ value moved here
...
16 |     rodio::play_once(&device, buffer).unwrap();
   |                               ^^^^^^ value used here after move

I’ve looked for some examples of how rodio is used, but haven’t been able to find anything that helps me to understand what to do. I want to avoid reading the file and creating a new buffer inside the audio thread each time an event is received. Does anyone have any ideas of what pattern I should be using for this use case?

To avoid to read the file twice, you just need to copy all the file content into a variable:

extern crate rodio;

use std::fs::File;
use std::io::Read; // read_to_end is part of the Read trait
std::io::Cursor; // to implement Read and Seek trait
fn main() {
    let file = File::open("1.wav").unwrap();
    let mut buf = Vec::new();
    file.read_to_end(&mut buf).unwrap();
    let cursor = Cursor::new(buf.clone());
    rodio::play_once(&device, cursor).unwrap().detach();
     /* ... */
}

Unfortunately, it impossible to call play_once twice with the same buffer, because it’s second parameter is Send + 'static. That mean you can’t pass a reference that doesn’t live the entire program. And I didn’t find alternative in rodio that allow the reuse of the same buffer, so this library may be unadapted for your use.

In its implementation, the play_once function calls Sink::new(), which in turn calls play_raw, which spawns a new thread. I guess this is not what you want.

I agree with @Yruama_Lairba: rodio may not suit your needs. I think it may be better idea to use cpal directly.

1 Like

Thank you for these pointers. I’ll investigate cpal itself in more detail.