Skip to content

Instantly share code, notes, and snippets.

@julianrubisch
Last active September 29, 2021 19:23
Show Gist options
  • Save julianrubisch/1f71a921f8b503158a3d9f8a0e750d4c to your computer and use it in GitHub Desktop.
Save julianrubisch/1f71a921f8b503158a3d9f8a0e750d4c to your computer and use it in GitHub Desktop.
Supercollider OOP - Part 1: Classes
(
Server.killAll;
~num_buffers = 4;
~num_channels = 12;
~rec_end = ~num_buffers.collect { 0 };
~gtranspose = ~num_buffers.collect { 0 };
~density = ~num_buffers.collect { 0.0 };
~stretch = 1.0;
~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);
~num_buffers.collect {
|bufnum|
var pattern_key = ('pattern_' ++ bufnum).asSymbol;
// RECORD
MIDIdef.noteOn(('rec_on_' ++ bufnum).asSymbol, {
|val, num, chan, src|
~rec = Synth(\rec, [\bufnum, ~buffers[bufnum]]);
}, (68 + bufnum));
MIDIdef.noteOff(('rec_off_' ++ bufnum).asSymbol, {
|val, num, chan, src|
~rec.set(\rec, 0);
}, (68 + bufnum));
// PLAYBACK
MIDIdef.noteOn(('pb_launch_stop_' ++ bufnum).asSymbol, {
if(Pbindef(pattern_key).isPlaying, { Pbindef(pattern_key).stop; }, { Pbindef(pattern_key, \end, ~rec_end).play; });
}, (64 + bufnum));
// DENSITY
// CC 46 "density" / Fader 1 on Channel 0, 2, 4, 6
MIDIdef.cc(('density_' ++ bufnum).asSymbol, {
|val, num, chan, src|
~density[bufnum] = val.asFloat.linlin(0.0, 127.0, 0.0, 1.0);
if(Pbindef(pattern_key).isPlaying, {
("DENSITY " ++ bufnum ++ ": " ++ ~density[bufnum]).postln;
Pbindef(pattern_key, \amp, Pseq([1.0, Pgeom(0.7, 0.9, 5)], inf) * Pif(Pfunc({ ~density[bufnum].coin }), Prand(~create_rests.(100), inf), Pseq([[Rest(0)!~num_channels]], inf)))
});
}, 46, bufnum * 2);
// TRANSPOSE
MIDIdef.cc(('transpose_' ++ bufnum).asSymbol, {
|val, num, chan, src|
~gtranspose[bufnum] = val.linlin(0, 127, -24, 24).round;
("GTRANSPOSE " ++ bufnum ++ ": " ++ ~gtranspose[bufnum]).postln;
if(Pbindef(pattern_key).isPlaying, { Pbindef(pattern_key, \gtranspose, ~gtranspose[bufnum]) });
}, 46, bufnum * 2 + 1);
};
// 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 "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);
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;
~buffers = ~num_buffers.collect { Buffer.alloc(s, s.sampleRate * 10.0, 1); };
s.sync;
})
)
(
~num_buffers.collect {
|bufnum|
Pbindef(('pattern_' ++ bufnum).asSymbol,
\instrument, \playback,
\bufnum, ~buffers[bufnum],
\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[bufnum],
\amp, Pseq([1.0, Pgeom(0.7, 0.9, 5)], inf) * Pif(Pfunc({ ~density[bufnum].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
);
};
VBufferCollectionTest : UnitTest {
test_newAllocBuffers {
var collection, server;
collection = VBufferCollection.new(server, 4, 44100);
this.assert(collection.buffers.every {|buffer| buffer.class == Buffer});
this.assert(collection.buffers.size == 4);
}
test_newLoadDirectory {
var collection, path, server;
path = PathName.new("~/Dropbox/tensor_samples");
this.bootServer;
collection = VBufferCollection.new(server, path, 44100, 1);
this.assert(collection.buffers.every {|buffer| buffer.class == Buffer});
}
test_newLoadFiles {
var collection, paths, server;
paths = PathName.new("~/Dropbox/tensor_samples").entries;
this.bootServer;
collection = VBufferCollection.new(server, paths, 44100, 1);
this.assert(collection.buffers.every {|buffer| buffer.class == Buffer});
}
}
VBufferCollection {
var <buffers;
*new { |server, numBuffersOrPaths, numFrames=0, numChannels=1|
^super.new.init(server, numBuffersOrPaths, numFrames, numChannels);
}
init { |server, numBuffersOrPaths, numFrames=0, numChannels=1|
buffers = case
{numBuffersOrPaths.class == PathName && {numBuffersOrPaths.isFolder}} {
// is a directory
numBuffersOrPaths.entries.select({ |fileOrDir| fileOrDir.isFile }).collect({
|path|
// Buffer.read(server, path.fullPath);
// TODO try to monofy in place
Buffer.readChannel(server, path.fullPath, channels: [0])
});
}
{numBuffersOrPaths.isArray && {numBuffersOrPaths.every { |path| path.class == PathName && path.isFile }}} {
// is an array of file paths
numBuffersOrPaths.collect { |path|
Buffer.read(server, path)
}
}
{numBuffersOrPaths.isInteger} {
numBuffersOrPaths.collect {
Buffer.alloc(server, numFrames, numChannels);
}
};
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment