Here’s a way to do “rhythmically skipping” through a file, without a large buffer and without phase precision issues.
At any time, you need three buffers:
- One of them is playing.
- Another may have just released (still fading out) – so it needs to remain stable.
- So the third one can be reading from disk.
These should be allocated with the longest duration of a segment you plan to play – not the entire sound file.
The Prout is the key. It “rotates” the buffers into their new roles for the next event to play, loads a segment for the following event, and then yields the buffer that should play this time.
Buffer.read
(at least in 3.11) passes the starting frame as an integer, so you get at least 31-bit precision (I forget if it’s treated as signed or unsigned – if signed, then you lose the sign bit). That’s the 6.xyz minutes we were talking about before * (2 ** 7) = over 768 minutes = over 12 hours, that you can access with sample accuracy.
This example, again, isn’t playing the sound file continuously. For that, I think elgiano’s suggestion is easiest (PlayBuf). (The thing that would be tricky about playing segmented buffers continuously is that language-server communication is not sample accurate. So the “hand-off” from one buffer to the next would have to be triggered within SynthDefs – it wouldn’t be completely smooth to do it with a pattern. It’s certainly possible, but a bit more than I have bandwidth to try right now.)
EDIT: This example is doing random access. To repeat the same segment rhythmically, it’s easier: no need to load a buffer on every iteration.
hjh
~path = "/media/dlm/7D53-8744/Docs/ardour/19-0808-quietcode-01/19-0808-quietcode-01-edit.wav";
f = SoundFile.openRead(~path);
~frames = f.numFrames;
~channels = f.numChannels;
f.close;
s.boot;
b = Array.fill(3, { Buffer.alloc(s, 88200, ~channels) });
(
SynthDef(\segment, { |out, bufnum, rate = 1, time = 1, amp = 0.1|
var eg = EnvGen.kr(Env.linen(0.01, time, 0.01), doneAction: 2),
sig = PlayBuf.ar(~channels, bufnum,
rate * BufRateScale.kr(bufnum));
Out.ar(out, sig * (eg * amp));
}).add;
)
(
p = Pbind(
\instrument, \segment,
\startFrame, Pwhite(0, ~frames - 100000, inf),
\bufnum, Prout { |ev|
var playing, releasing, loading, temp;
#playing, releasing, loading = b;
// first time through the loop, haven't loaded anything
// load something, and rest (because 'playing' isn't ready yet)
loading.read(~path, ev[\startFrame]);
ev = Rest(playing).yield;
loop {
// playing is now the *previous* buffer that was playing
// playing --> releasing etc.
temp = playing;
playing = loading; // was loading, should play next
loading = releasing; // released last time, should load next
releasing = temp;
loading.read(~path, ev[\startFrame]); // to be ready next time
ev = playing.yield; // use this buffer as 'bufnum'
}
},
\dur, Pwhite(1, 4, inf) * 0.25, // rhythmically
\time, Pfunc { |ev| ev[\dur] / thisThread.clock.tempo },
\amp, 1
).trace.play;
)
p.stop;