Hey everybody, I hope it's okay for me to start a broader discussion on a well trodden path...

I am trying to reach the final frontier of Pure Data which is achieving truly sample accurate looping of many long wav files that stay in sync and don't degrade over time 😉

My current setup is this:
Audio files are loaded into arrays on loadbang using soundfiler and arrays are resized to the total sample length of audio file.

When the audio file is needed, the array is set, then read using the phasor and tabread4 objects from the iem double precision library: https://git.iem.at/pd/iem_dp

My choice of these objects is because I found that the vanilla phasor~ and tabread4~ objects would result in synchronisation issues between audio files after a few minutes of looped playback. Thankfully, this issue is totally negated by use of the double precision objects.

I'm overall quite happy with this setup but there are a few issues that I would love to find a workaround for.

  1. There is an imposed limit on the amount of audio files I can use. The patch I have created is being embedded on iOS using libpd, and depending on the device there is a variable limit on the total size of all the arrays contained within the patch. The easiest way I've gotten around this is to lower the sample rate of the wav files to allow for more musical material in smaller arrays but of course this comes with the cost of degraded sound quality.

  2. Loading 100s of megabytes of wav files into arrays on startup takes quite a while.

  3. Audio files are guaranteed to be different lengths (all relative to the bpm of the composition so they loop in a musical way) but so far I see no feasible option of defining a sample length or multiple there of and sticking to it.

It is essential that I can access the audio samples immediately, I have seen a method on this forum that I really like which involves loading the first few seconds of an audio file to an array, reading that array with tabplay~ then sending a message to a readsf~ object with a onset to takeover playback when tabplay~ has finished reading the array: https://forum.bela.io/d/709-reading-multiple-samples-and-cpu-overload

This works wonders for oneshot sounds but I cannot find a way to translate this method into looping sounds without having audible clicks and the loop point of the sample.

I have also come across the readanysf~ external which promises sample accurate looping: https://puredata.info/downloads/readanysf but I cannot successfully build it to try out for myself.

I would greatly appreciate your thoughts on the matter, I absolutely love Pd and I really want it to delivery and get over this hurdle! 😃

Thanks

I can only guess that the issue with looping here is because the file does not have a length that is a multiple of the block size: at a quick inspection, the [bang( you receive when [readsf~] stops is not sample-accurate. You could try looping a file with n*64 (or whatever blocksize you are using) samples and see if that loops nicely ... if this work, then if you cannot modify your files, the easiest solution would be to try changing the blocksize of each subpatch to the largest value that is a submultiple of the file length (worst case, 1, but that will increase the CPU usage significantly).

There may be other clever functions where once you start playing back, you use readsf~ to populate a pair of arrays (used as a double buffer) and then always read from the arrays, but again that may have all the same issues as the strategy above. You could consider crossfading the end of the file with the start of the file if it's supposed to be looping ...

Or you could avoid relying on readsf~'s bang and instead use a [delay] object to detect the actual end of the file and see if you can use that to control the [phasor~] that reads the "head" array with a known phase and also correcting for the difference in offset by passing an appropriate offset to [readsf~] in the ensuing [open ...( command.

But anyhow the easiest thing is probably to modify readsf~ to add a loop flag (shouldn't be too complicated, I suspect), or just use [readanysf~] and call it a day.

i used two instances of [readsf~] for the same file and toggled between them, loading one while the other is playing.
not sure if that is instantaneous access enough for your purposes.
if the [readsf~] end-of-file bang is not reliable, i would probably go the timer route (closest multiple of blocksize under file sample size), use that instead of the end-of-file, and implement a crossfade.

or closest over sample size, so you don't need a crossfade.. i doubt if you will hear 63 samples of silence, but that's to be tested.
advantage of using the system timer is that your deviation will not accumulate if done right.