You didn’t notice actual discontinuities in the signal?
If I run the original code with a 256-sample hardware buffer (in JACK, I have 256 x 2 periods):
![scbuf-broken-01-256](https://scsynth.org/uploads/default/original/2X/d/deccba6bf1a35017eed58911bd4f231bffd964d6.png)
- From the beginning… scsynth processes 256 samples, then 256 more, then 256 more, and 256 more.
- Now Phasor will hit the end, and wrap back around to 0.
- The trigger fires SendReply. This message takes a little time to reach the language client.
- The server continues processing the next 256 samples.
- Meanwhile, the language has received the message and sent the request to the server for the buffer-ful of data. At this point, the buffer contains 256 samples of new data, and 768 samples of old data.
etc. etc… the plot shows 2048 samples = 2x1024. In the first half, the first quarter of the (256 samples) don’t line up with the rest; same in the second half. Even for visualization, I think this is not what you want.
The programming principle at play is about reading and writing from/to the same collection – in a database, if you are writing to and querying the same table concurrently, table locking is essential – otherwise, part of the query might resolve before a write, and another part after, so the result would be inconsistent.
It is not safe to read and write at the same time from the same area. But the original suggestion always reads the entire buffer – with that approach, it’s impossible to ensure good data.
A better approach is to allocate a larger buffer, and always read from the section that has just finished writing (but which is not being written now).
(
var chunkSize = 1024;
var numChunks = 8;
var relay_buffer = Buffer.alloc(s, chunkSize * numChunks);
var synth = {
var sig = SinOsc.ar(220);
var phase = Phasor.ar(0, 1, 0, chunkSize);
// btw this is already guaranteed to be only a single-sample trigger
// Trig.ar is not needed
var trig = HPZ1.ar(phase) < 0;
var partition = PulseCount.ar(trig) % numChunks;
BufWr.ar(sig, relay_buffer, phase + (chunkSize * partition));
SendReply.ar(trig, '/buffer_refresh', partition);
sig;
}.play;
a = Signal.new;
OSCdef(\k, { |msg|
// the partition to retrieve is the one BEFORE the latest transition point
var partition = (msg[3] - 1) % numChunks;
// also I'm using getn
// loadToFloatArray goes through a disk file
// that is faster for long collections
// for short collections, this is very inefficient
// you do not need to create/destroy 43 temp files per second here!
relay_buffer.getn(partition * chunkSize, chunkSize, { |data|
// here I'll be lazy and assume messages arrive in sequence
// though in theory this may not be the case?
a = a ++ data; // this is for my test plot, you can delete
// floatArrayToList not needed
~o.sendMsg(\waveform, *(data.as(Array)));
});
}, '/buffer_refresh');
synth.onFree {
OSCdef(\k).free;
// original code example leaks buffer references
// that is also not a good habit
relay_buffer.free;
};
c = synth;
)
c.free;
a[0..2047].plot;
And it’s clean.
![scbuf-fixed-01-256](https://scsynth.org/uploads/default/original/2X/3/399cbafbce344a71682d2b93861d1352955cb9cd.png)
As stated elsewhere… I am trying to reduce my involvement on the forum, because of work and other pressures. But, this forum is also a resource for users facing a similar question in the future; it’s not quite ideal to leave a suggestion in place that makes a problem look easier than it really is, while producing an invalid result… I had hoped someone else might step up to fix it, but, didn’t happen. Anyway, hope that helps.
hjh