Skip to content

Instantly share code, notes, and snippets.

@julianrubisch
Created March 17, 2021 11:37
Show Gist options
  • Save julianrubisch/7abd957c55ab978514139bfa51c0a669 to your computer and use it in GitHub Desktop.
Save julianrubisch/7abd957c55ab978514139bfa51c0a669 to your computer and use it in GitHub Desktop.
Supercollider Variable Length Looper
(
Server.killAll;
~gtranspose = 0;
~rec_end = 0;
~stretch = 1.0;
~density = 0.0;
~num_channels = 12;
~pitch_env_amp = 0.0;
~pitch_range = 0.0;
~pitch_mod_amp = 0.0;
~create_notes = {
|variation_count=10|
Array.fill(variation_count, {[
Array.fill(~num_channels, {[0, 2, 3, 7, 4, 2].choose}).scramble
]})
};
~create_transpositions = {
|variation_count=10|
Array.fill(variation_count, {[
Array.fill(~num_channels, {[0, 12, -12, 5, 7, -7].wchoose([0.5, 0.15, 0.15, 0.1, 0.5, 0.5])}).scramble
]})
};
~create_rests = {
|variation_count=10|
Array.fill(variation_count, {[
Array.fill(~num_channels, {[1, Rest(0)].choose}).scramble
]})
};
~create_rqs = {
|variation_count=10|
Array.fill(variation_count, Array.exprand(~num_channels, 10.0, 1.0))
};
ServerTree.removeAll;
s.newBusAllocators;
s.options.memSize = 2.pow(20); // ca. 1GB
MIDIClient.init;
MIDIIn.connectAll;
MIDIFunc.trace(false);
// note 68 record
MIDIdef.noteOn(\rec_on, {
|val, num, chan, src|
~rec = Synth(\rec, [\bufnum, b]);
}, 68);
MIDIdef.noteOff(\rec_off, {
|val, num, chan, src|
~rec.set(\rec, 0);
}, 68);
// note 64 launch
MIDIdef.noteOn(\pb_launch, {
// if(~pb.isPlaying, {~pb.set(\t_stop, 1)});
if(Pbindef(\pattern).isPlaying, { Pbindef(\pattern).stop; });
// ~pb = Synth(\playback, [\bufnum, b, \end, ~rec_end, \rate, ~rate]).register;
Pbindef(\pattern, \end, ~rec_end).play;
}, 64);
// note 65 stop
MIDIdef.noteOn(\pb_stop, {
// ~pb.set(\t_stop, 1);
Pbindef(\pattern).stop;
}, 65);
// CC 112 transpose
MIDIdef.cc(\transpose, {
|val, num, chan, src|
// ~rate = val.linlin(0, 127, -2.0, 2.0);
~gtranspose = val.linlin(0, 127, -24, 24).round;
if(Pbindef(\pattern).isPlaying, { Pbindef(\pattern, \gtranspose, ~gtranspose) });
}, 112);
// CC 32 "stretch"
MIDIdef.cc(\stretch, {
|val, num, chan, src|
~stretch = val.asFloat.linexp(0.0, 127.0, 1.0, 100.0);
if(Pbindef(\pattern).isPlaying, { Pbindef(\pattern, \stretch, ~stretch) });
}, 32);
// CC 28 "pitch_range"
MIDIdef.cc(\pitch_range, {
|val, num, chan, src|
~pitch_range = val.asFloat.linexp(0.0, 127.0, 1.0, 2.0) - 1.0;
if(Pbindef(\pattern).isPlaying, { Pbindef(\pattern, \pitch_range, ~pitch_range) });
}, 28);
// CC 10 "pitch_mod_amp"
MIDIdef.cc(\pitch_mod_amp, {
|val, num, chan, src|
~pitch_mod_amp = val.asFloat.linlin(0.0, 127.0, 0.0, 10.0);
if(Pbindef(\pattern).isPlaying, { Pbindef(\pattern, \pitch_mod_amp, ~pitch_mod_amp) });
}, 10);
// CC 46 "density" / Fader 1 on Channel 0
MIDIdef.cc(\density, {
|val, num, chan, src|
~density = val.asFloat.linlin(0.0, 127.0, 0.0, 1.0);
if(Pbindef(\pattern).isPlaying, {
~density.postln;
Pbindef(\pattern, \amp, Pseq([1.0, Pgeom(0.7, 0.9, 5)], inf) * Pif(Pfunc({ ~density.coin }), Prand(~create_rests.(100), inf), Pseq([[Rest(0)!~num_channels]], inf)))
});
}, 46, 0);
// CC 46 "pitch envelope amplitude" / Fader 2 on Channel 1
MIDIdef.cc(\pitch_env_amp, {
|val, num, chan, src|
~pitch_env_amp = val.linlin(0, 127, -24, 24).postln;
if(Pbindef(\pattern).isPlaying, { Pbindef(\pattern, \pitch_env_amp, ~pitch_env_amp) });
}, 46, 1);
s.waitForBoot({
s.freeAll;
Buffer.freeAll;
s.sync;
SynthDef(\rec, {
|in=0, bufnum=0, rec=1|
var sig = SoundIn.ar(in),
stopTrig = (rec <= 0),
phase = Phasor.ar(0, 1, 0, BufFrames.kr(bufnum));
// RecordBuf.ar(sig, bufnum, doneAction: 2, loop: 0); replaced with BufWr
BufWr.ar(sig, bufnum, phase);
SendReply.ar(K2A.ar(stopTrig), '/recEnded', phase.poll);
FreeSelf.kr(stopTrig);
}).add;
// see https://scsynth.org/t/looper-with-a-variable-length/818/6
OSCdef(\ended, { |msg|
// msg is ['/recEnded', nodeID, replyID, value0...]
// so the data point is msg[3]
~rec_end = msg[3]; // save ending frame index
if(Pbindef(\pattern).isPlaying, { Pbindef(\pattern, \end, ~rec_end) })
}, '/recEnded', s.addr);
SynthDef(\playback, {
|out=0, bufnum=0, start=0, end=44100, loop=0, t_stop=0, t_trig=0, rate=1, pan=0, atkcrv=1, relcrv=(-10), gtranspose=0|
var sig, phasor, env, t_gate;
var dur = \dur.kr / \tempo.kr(1);
var atk = \atk.kr(0.002);
var sus = \sus.kr(0);
var rel = \rel.kr(0.2);
end.poll;
t_gate = \t_gate.kr(Array.fill(~num_channels, 1));
env = Env.new([0, 1, 1, 0], [atk, sus, rel], [atkcrv, 0, relcrv]).kr(gate: t_gate, timeScale: dur * \env_stretch.kr(1.0), doneAction: 2);
phasor = Phasor.ar(
trig: t_trig,
rate: BufRateScale.kr(bufnum) * (\note.kr(Array.fill(~num_channels, 0)) * \pitch_range.kr(~pitch_range)
+ gtranspose
+ Env.perc(atk, rel, \pitch_env_amp.kr(0.0), -12).ar(gate: t_gate)
+ LFNoise1.ar(6, \pitch_mod_amp.kr(0.0))
).midiratio,
start: start,
end: end,
resetPos: start
);
sig = BufRd.ar(1, bufnum, phasor, loop);
sig = BLowPass.ar(sig, Env([\filter_freq.kr(200), Rand(5000, 3000) + \filter_freq.kr(200), Rand(300, 0) + \filter_freq.kr(200)], [atk, rel], [3, 0]).kr(gate: t_gate, timeScale: dur), rq: \rq.kr(Array.fill(~num_channels, 1)));
sig = sig * env * (\amp.kr(Array.fill(~num_channels, 0.5)) * (-3*~num_channels).dbamp);
sig = Splay.ar(sig);
Out.ar(out, sig);
}).add;
s.sync;
b = Buffer.alloc(s, s.sampleRate * 10.0, 1);
s.sync;
})
)
(
Pbindef(\pattern,
\instrument, \playback,
\bufnum, b,
\tempo, Pfunc {thisThread.clock.tempo},
\dur, Pwrand([1, 2, 4, 6, 8, 12], [80, 20, 5, 3, 2, 1].normalizeSum, inf) / 8, // in beats, (default tempo clock)
\note, Prand(~create_notes.(100), inf) + Prand(~create_transpositions.(100), inf),
\gtranspose, ~gtranspose,
\amp, Pseq([1.0, Pgeom(0.7, 0.9, 5)], inf) * Pif(Pfunc({ ~density.coin }), Prand(~create_rests.(100), inf), Pseq([[Rest(0)!~num_channels]], inf)),
\start, 0, //Pbrown(0, ~rec_end * 0.75, 1000, inf),
\end, ~rec_end,
\filter_freq, Pseq([60, 66, 70, 72].collect {|midinote| midinote.midicps}, inf),
\rq, Prand(~create_rqs.(100), inf), // multichannel expand
\atkcrv, Pseq(Array.interpolation(64, -5, -3), inf), // increasing ramp
\atk, Pexprand(0.005, 0.5, inf),
\rel, Pexprand(0.2, 0.6, inf),
\sus, Pexprand(0.02, 0.15, inf),
\env_stretch, ~stretch,
\pitch_env_amp, ~pitch_env_amp,
\pitch_range, ~pitch_range,
\pitch_mod_amp, ~pitch_mod_amp
);
)
MIDIFunc.trace(true);
TempoClock.default.tempo_(1.2);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment