Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewind the IO object #32

Open
alxx opened this issue Mar 19, 2020 · 4 comments
Open

Rewind the IO object #32

alxx opened this issue Mar 19, 2020 · 4 comments

Comments

@alxx
Copy link

alxx commented Mar 19, 2020

How can I rewind the Reader so that I can go back and read the first buffer? I'm trying to loop a wave file but currently the only way is to recreate the Reader object once I get to the end of the samples, which is expensive.

@jstrait
Copy link
Owner

jstrait commented Mar 25, 2020

@alxx Unfortunately, there's currently no way to rewind a Reader, or to seek in general. Besides creating multiple Reader instances like you mentioned, I suppose another option if your file is small enough would be to read it into one large buffer and loop through that.

I’ve been planning to add something like a Reader#seekToSampleFrame() method but haven’t gotten around to it yet. The idea is that it would work similarly to IO#seek() but instead of seeking by individual bytes, would allow seeking by sample frame. For example, to go back to the first sample frame you would use my_reader.seekToSampleFrame(0).

I’ll take a look and see in more detail what would be involved in adding that functionality.

@alxx
Copy link
Author

alxx commented Mar 25, 2020

Well, first my solution has been to create new Reader instances and trust the garbage collector:

# Rewrite the exception handling to loop the same file continuously
module WaveFile
  class Reader
    def read(sample_frame_count)
      if @closed
        raise ReaderClosedError
      end

      begin
        @data_chunk_reader.read(sample_frame_count)
      rescue # always read from the start when reaching the end
        @io.rewind
        riff_reader        = ChunkReaders::RiffReader.new(@io, format)
        @data_chunk_reader = riff_reader.data_chunk_reader
        @sample_chunk      = riff_reader.sample_chunk
      end
    end
  end
end

Then I noticed that the Reader was anyway too slow for real-time operations (feeding the samples into a PortAudio stream on a pretty powerful iMac) causing hick-ups every few seconds when I was reading chunks as "large" as 4096 samples at a time. So I reverted to reading the entire file (220500 samples) into a buffer and looping through that, as you guessed.

Thanks for looking into this! :)

@jstrait
Copy link
Owner

jstrait commented Mar 30, 2020

Oh interesting! I’ve never tried using the gem for real-time stuff, so unfortunately I don’t have any particular insights to add about that. How did you determine garbage collection was causing the hiccups? It makes sense why it would, but interested to know how to recreate. How are you sending the samples to PortAudio?

If garbage collection is a blocker, it sounds like a seekToSampleFrame() method wouldn't fix your problem, although it would have removed the need to create a custom version of read().

@alxx
Copy link
Author

alxx commented Mar 30, 2020

I'm not sure it was the GC itself, actually the GC is operating out-of-band as far as I know, so it's not a likely culprit (though I may be wrong).

I use ffi-portaudio, a Ruby implementation where I keep supplying sample buffers to a C library that speaks to the sound card. If I don't supply samples in time, it causes hiccups. I prepare buffers in advance in a Ruby Queue but when there's just no data (for example because Wavefile can't read them fast enough) then I'm forced to just supply zeroes. Then it's another kind of hiccup :))

Man, streaming is really not easy...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants