2

I am using Rust with the Rodio crate and I wanted to make a Vec of loaded sources to use whenever they are needed, so that the program doesn't need to load it every time. I've made a SoundHandler class that contains an OutputStream, an OutputStreamHandle and a Vec<Decoder<BufReader<File>>>. The first two are instanced by using OutputStream::try_default(), which returns both in a tuple. The Vec is a vector of loaded sources. Its content are, as I understand it, loaded and then pushed in the following SoundHandler method:

pub fn load_source(&mut self, file_path: PathBuf) -> SoundId {
    let id = self.sources.len();
        
    self.sources.push(
        Decoder::new(
            BufReader::new(
                File::open(file_path).expect("file not found")
            )
        ).expect("could not load source")
    );

    SoundId(id)
}

Next, the source should be played by calling the play_sound() method.

Previously, it directly loaded and played the sound:

pub fn play_sound(file_path: PathBuf) {
    self.stream_handle.play_raw(
        Decoder::new(
            BufReader::new(
                File::open(file_path).expect("file not found")
            )
        ).expect("could not load source")
    );
}

But now it must handle the sources that come from the Vec. I had trouble while trying to do that. I need help with that. I am pretty new to the language so knowing how to solve that problem in more than one way would be even better, but my priority is really the path I've chosen to do it, because knowing how to solve that would undoubtedly increase my Rust problem solving skills. Keep in mind my main goal here is to learn the language, and the snippets below are not at all an urgent problem to be solved for a company.

Ok, so I tried the most straightforward thing possible. I directly changed the old method to do the same thing but with the Vec:

pub fn play_sound(&self, sound_id: SoundId) {
    self.stream_handle
        .play_raw(self.sources.get(sound_id.0).unwrap().convert_samples()).unwrap();
}

But the compiler won't compile that method, because, according to it,

error[E0507]: cannot move out of a shared reference
        self.stream_handle.play_raw(self.sources.get(sound_id.0).unwrap().convert_samples()).unwrap();
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-----------------
                                    |                                     |
                                    |                                     value moved due to this method call
                                     move occurs because value has type `rodio::Decoder<std::io::BufReader<std::fs::File>>`, which does not implement the `Copy` trait
note: this function takes ownership of the receiver `self`, which moves value

I understand what the compiler is saying. Of course that can't be possible. convert_samples() tries to make the value inside the Vec invalid, and thus it could make the Vec unsafe. So I naively tried to clone the Decoder, but the struct does not implement the Clone trait, and apparently does not have any method to do so. Finally, I found a way to get the value and own it through the Vec::remove() method, but I do not want to remove it, and I do want it to be fallible.

So my question is: in this situation, how can I make that Vec of loaded sources? Maybe I could even make a Vec out of SamplesConverter. But then the name of the type gets huge, so that is probably not an intended way to do it: Vec<SamplesConverter<Decoder<BufReader<File>>, f32>>. But it has the same problems of the other implementation.

Felipe
  • 23
  • 5
  • 1
    Using [`buffered`](https://docs.rs/rodio/0.17.0/rodio/source/trait.Source.html#method.buffered), you get a `Source` that can be cloned, so put the `Buffered` sources in your `Vec` and clone them for playing. – Jmb Feb 20 '23 at 07:57
  • Worked flawlessly. Could you make that an answer for it to be eligible for accepting? – Felipe Feb 20 '23 at 14:00
  • By the way, is having a clonable "version" of a struct a common Rust thing? I had never seen something like that before. If I knew that was a thing, I would not have asked this in the first place. – Felipe Feb 20 '23 at 14:22
  • @Jmb I've just researched the topic of commenting as an answer. Should I delete the post? I mean, I had never seen anything like that before. So I did not know what I was looking for. Now I even found another stackoverflow question about something roughly similar, and for me that method in the documentation, `buffered`, made no sense, because it was not a buffer I was looking for, but a way to store. What should I do now? Delete the question? Answer it myself? Answer it with a reference to the other post? Leave it? This is my first ever question on stackoverflow. – Felipe Feb 20 '23 at 14:49
  • I don't see any reason to delete the post, it is clearly written and asks a legitimate question so it may be useful to someone else. It could be improved by adding a more complete [mre] and links to the relevant crates, but other than that it's a good question. – Jmb Feb 20 '23 at 15:35

1 Answers1

1

Decoders can't be cloned because the underlying data source may not be clonable (and in fact BufReader isn't). So in order to clone the audio sources you will need to ensure that the data is stored in memory. This can be accomplished by the buffered method, which returns a Buffered source, which can be cloned. So something like this should work:

pub fn load_source(&mut self, file_path: PathBuf) -> SoundId {
    let id = self.sources.len();
        
    self.sources.push(
        Decoder::new(
            BufReader::new(
                File::open(file_path).expect("file not found")
            )
        ).expect("could not load source")
        .buffered()
    );

    SoundId(id)
}

pub fn play_sound(&self, sound_id: SoundId) {
    self.stream_handle.play_raw(
        self.sources.get(sound_id.0).unwrap().clone().convert_samples()).unwrap();
}
Jmb
  • 18,893
  • 2
  • 28
  • 55