bastibe/PySoundCard

How to loop a whole scipy.wavfile

Closed this issue · 9 comments

The examples for looping 5 seconds are quite nice, I can't quite get my head round how to loop a whole file though, seems like it should be straightforward ?

The examples do two different things:

  1. "loop back", which is supposed to mean "shove all data from the inputs directly to the outputs"
  2. play a whole audio file

Example 2 already uses the whole file, example 1 doesn't use any file at all.

BTW, I would recommend using a callback for things like example 1 (as described in the section "Callback Mode"); the combination of write() and read() might lead to gaps in the audio playback.

In the future, we will implement additional convenience functions which will make the playback of a NumPy array much simpler, see #19.

I've got this so far (based on various internet searches):

    CHUNK = 1024

    # Open Wave File and start play!
    rate, wave = wavread(sys.argv[1])
    wave = np.array(wave, dtype=np.float32)
    wave /= 2**15 # normalize -max_int16..max_int16 to -1..1

    block_length = 16

    stream = Stream()
    stream.start()
    pos=0
    while self.loop:
        data = wave[pos:pos+self.CHUNK*2]
        stream.write(data)
        pos += CHUNK*2
        if pos > len(wave):
            pos = 0
    stream.stop()

I don't get any buffer underruns, but there are a couple of things I think might be dodgy ... the CHUNK, and also - just looping straight back to zero ... fine on my own looping wav, but seems like this could do with either a circular buffer, or a wav where I add a small sample onto the end of the wave, that can be read on looping.

Ah, just noticed your comments - will try just doing something more based on example1

OK, I get it, you want to play back a sound repeatedly in a loop.
This "loop back" stuff is really confusing, we should probably change this to "pass through" or something ...

Beware that most of the examples you'll find on the internet are unnecessarily complicated and often broken in various ways.

You don't have to use any explicit chunking, you can just use:

while self.loop:
    stream.write(wave)

This should play the sound repeatedly, but there may be gaps between the repetitions.
If you want to avoid gaps, you should use the callback API.
Within the callback, you get the current blocksize (which can change between calls if you use block_length=0) from the length of the input data.
There is no need to specify a separate chunk size.

a wav where I add a small sample onto the end of the wave, that can be read on looping.

I don't understand, can you please explain?

Sorry that was a bad sentence :)

I was trying to work out how to avoid gaps:

[block]block]
[wave file]..

So I was imagining I could pad the end of the wave with it's beginning, and start the next block at the offset:

[block][block]
[wave file][wa

Anyway, callback API seems to be the right way.

I was a bit confused by the chunk size, stuff - I was basically porting some pyaudio code I found in a gist to pysoundcard.

I'll have a look on the high level API ticket it looks quite interesting.

OK, turns out I'm still pretty confused. ..

Writing the whole wave to the Stream works great, except I can't seem to get playback to stop when I press Ctrl-C.

I tried hooking up a signal handler to set a variable, so that the callback would return False instead of continue_flag, that didn't help - the same for catching KeyboardInterrupt.

Started on a callback variation, think I'm misunderstanding wavfile though:

from pysoundcard import Stream, continue_flag
from scipy.io.wavfile import read as wavread

import numpy as np
import time
import sys

fs, wave = wavread(sys.argv[1])
wave = np.array(wave, dtype=np.float32)
wave /= 2**15 # normalize -max_int16..max_int16 to -1..1

pos=0

def callback(in_data, out_data, time_info, status):
    global wave, pos
    data_len = len(in_data)
    pos += data_len % len(wave)
    out_data[:] = wave[pos:pos+data_len] 
    return continue_flag

block_length = 16

s = Stream(sample_rate=44100, block_length=block_length, callback=callback)
s.start()
time.sleep(5)
s.stop()

Here's an easy way to play a whole sound file one block at a time:

with Stream(44100) as s:
    for block in blocks('filename.wav', 1024):
        s.write(block)

Cool, what is 'blocks' in this case, where do I import it from ?

That's pysoundfile.blocks