ruuda / hound

A wav encoding and decoding library in Rust
https://codeberg.org/ruuda/hound
Apache License 2.0
482 stars 63 forks source link

Smpl chunk: play loops #53

Closed paucazou closed 3 years ago

paucazou commented 3 years ago

Hello, I've come to your library thanks to rodio and cpal. I need to play wave files that contain 'smpl' chunk and play forever the loop stored in this chunk (forever, ie, until the user wants to stop the sound). I've figured out how to parse this chunk (it's pretty easy) but don't know how to perform the loop. I don't know it you're interested in implementing this kind of features, but can you help me and just say where I should start? Thanks in advance,

Philippe

NB: my informations about the smpl chunk comes from here: http://www.piclist.com/techref/io/serial/midi/wave.html

ruuda commented 3 years ago

Thanks for taking the time to open an issue. I’m not sure I understand your question correctly. Hound itself only reads wave files, it doesn’t handle playback. If you want to loop some audio, you can read the samples into a buffer, and repeatedly send that same buffer to the audio playback library. It looks like the smpl chunk contains some metadata about the audio data that is needed by a sampler if you want to use the audio as the basis of a virtual instrument. Parsing that chunk is something that could be a nice feature for Hound, but to then use that information, for example if you want to transpose the audio data to a certain note, you’ll need a DSP or resampling library.

paucazou commented 3 years ago

Thanks for your quick answer. I'm sorry that my question wasn't very clear and indeed she was confusing. When I said 'play', I was thinking 'read'. The smpl chunk contains at its ends a list of loops, particularly the start and the end of the loop, as two byte offsets. I want my program to read the chunk data but, when it reach the loop endpoint, goes back to the startpoint. My problem is that I don't know which part of the code to change.

ruuda commented 3 years ago

I want my program to read the chunk data but, when it reach the loop endpoint, goes back to the startpoint.

I think what you want to do instead, is read the audio data into a buffer, and then do the looping in the piece of code that is sending the samples to your audio playback library; not in the code that is reading the samples. Something like this:

struct Loop {
    samples: Vec<i16>,
    begin: usize,
    end: usize,
    pos: usize,
}

impl Loop {
    pub fn new<R>(reader: WavReader<R>) -> Loop {
        // Read samples here, and read begin/end manually from SMPL chunk.
        // Support for parsing SMPL could possibly be added to Hound.
    }

    // Called periodically by the audio playback system.
    pub fn fill_buffer(&mut self, buffer: &mut [i16]) {
        for x in buffer.iter_mut() {
             *x = self.samples[self.pos];
             self.pos += 1;
             if self.pos >= self.end {
                 self.pos = self.begin;
             }
        }
    }
}

The smpl chunk contains at its ends a list of loops, particularly the start and the end of the loop, as two byte offsets.

Hmm, in order to convert those byte offsets to sample indices, you need to divide by the number of bytes per sample. Usually the number of bytes per sample is the reader.spec().bits_per_sample / 8, but the format does allow for other combinations in some cases. The number of bytes per sample is available in the WavReader internally, but at the moment it is not exposed.

paucazou commented 3 years ago

Thanks for your answer. I'll try to modify Hound to implement your suggestion. Thanks again!