tl;dr seems like it works with one buffer, one BufRd
, and two Phasor
s?
Lots of great stuff here, and thanks very much for the tip about NamedControl
!
From the examples above, if I know in my client (scsynth or whatever way I’m sending OSC to scsynth) how long I want the loop to be, then there’s a good way to proceed. Make a synth play a loop of the required length. I’m being difficult, though, in the interest of having less client-side logic and also (maybe only marginally) improving the accuracy of the loop triggering.
To clarify my intended question, pretend that I only have access to a very very minimal client. I only have the ability to send a pair of OSC pre-determined messages to scserver. In particular, these messages don’t contain the loop length! The second message is sent some time after the first. Is it possible to compute the loop length on the server?
Below is my current attempt. It resolves the main question, I think (yes), but I’m of course never sure I’m doing things correctly/efficiently/robustly/safely/idiomatically, so I’d love more comments.
First, to demonstrate the approach, here’s an example which fills a buffer with the test sound, sends a first message to create the a looping synth, and sends a second message to start looping. The trick is to use a pair of Phasor
s, which move in parallel until the loop is triggered. The value of the first determines the loop length of the second. The second is used to read from the buffer. When the loop is triggered, the first phasor freezes.
I like this approach because, in addition to satisfying my criteria about only controlling things with a pair of (pre-determined) messages to the server, this only involves one buffer and one BufRd
.
// Test to start looping from a buffer with a simple message to the server
(
// Read the test sound into a mono buffer
b = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav");
SynthDef(\looper_prerec, {
arg
out = 0,
bufnum = 0,
loop_on = 0;
var
play,
play_head,
loop_length,
looping;
// Allow a single change from 0.0 to 1.0
looping = SetResetFF.kr(loop_on);
// The loop length tracks the "play head" until looping = 1.0, when it freezes
loop_length = Phasor.ar(
trig: 0,
rate: (1.0-looping) * BufRateScale.kr(bufnum),
start: 0,
end: BufFrames.kr(bufnum)
);
// Play from the buffer with the given loop length
play_head = Phasor.ar(
trig: 0,
rate: BufRateScale.kr(bufnum),
start: 0,
end: loop_length
);
play= BufRd.ar(1, bufnum, play_head);
Out.ar(out,Pan2.ar(play))
}).send(s);
)
~my_synth = Synth("\looper_prerec",[\bufnum,b]);
~my_synth.set("\loop_on",1.0);
Once that works, here’s my attempt at doing it while recording to your own buffer.
// Similar, but record from an audio input
(
b = Buffer.alloc(
server: s,
numFrames: s.sampleRate * 10.0, // 10 seconds maximum
numChannels: 1
);
// Define Synth
SynthDef(\looper_thing, {
arg
in_channel = 1, // 1-based for AudioIn.ar
out = 0,
bufnum = 0,
start_looping = 0;
var
play,
play_head,
loop_length,
looping;
// Only allow loop to be turned on, and only once
looping = SetResetFF.kr(start_looping);
// The loop length is the same as the play head until "frozen" by looping = 1.0
loop_length = Phasor.ar(
trig: 0,
rate: (1.0-looping) * BufRateScale.kr(bufnum),
start: 0,
end: BufFrames.kr(bufnum));
// Record to our buffer
RecordBuf.ar(
inputArray: AudioIn.ar(in_channel),
bufnum: bufnum,
loop: 0
);
// Play from our buffer with the computed loop length
play_head = Phasor.ar(
trig: 0,
rate: BufRateScale.kr(bufnum),
start: 0,
end: loop_length);
play = BufRd.ar(1, bufnum, play_head);
Out.ar(out,Pan2.ar(play));
}).send(s);
)
// First message - create the synth. Start making noise immediately!
~my_synth = Synth("\looper_thing",[\bufnum,b]);
// Second message - start looping, once you've made enough noise
// (Note: if you wait until after the buffer is full, it'll start looping the whole things, and sending this message will loop part of it)
~my_synth.set("\start_looping",1.0);