Skip to content

Instantly share code, notes, and snippets.

@antonhornquist
Last active May 13, 2016 19:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save antonhornquist/d35906a90ccd4a872ea8b0e279641145 to your computer and use it in GitHub Desktop.
Save antonhornquist/d35906a90ccd4a872ea8b0e279641145 to your computer and use it in GitHub Desktop.
// This is a step sequencer with 8 tracks and 8 steps. The sequencer triggers playback of one sample per
// track. Sequencer tempo and swing amount is variable.
// The code below is split in two main sections (core + monome and arc UI) followed by an example of
// loading samples for the tracks, a pattern and tempo and swing amount setting.
(
// Section 1: Define the core step sequencer functionality. All step sequencer functionality is available
// using the exported functions.
var num_samples = 8, num_pattern_steps = 8, pattern_beats = 2, default_tempo = 100, default_swing_amount = 50;
var buffers, trigs;
var current_swing_amount, current_swing_offset, current_tempo, current_pattern;
var spawn_pattern, player, get_playpos, playpos;
var get_tempo, adjust_tempo, set_tempo, get_swing_amount, adjust_swing_amount, set_swing_amount, clear_pattern, toggle_trig, trig_is_set, play, stop;
var load_sample, add_trigs_dependant, add_player_dependant;
var tempo_spec = ControlSpec(20, 300, step: 1);
var swing_amount_spec = ControlSpec(0, 100, step: 1);
var get_tempo_spec = { tempo_spec };
var get_swing_amount_spec = { swing_amount_spec };
var set_event_type;
var event_type = [\note];
s.serverRunning.not.if {
Error("Boot server stored in interpreter variable s.").throw
};
// Define and add synthdef
SynthDef(\sampleplayer, { |out = 0, bufnum|
OffsetOut.ar(
out,
Pan2.ar(PlayBuf.ar(1, bufnum, BufRateScale.kr(bufnum), doneAction: 2))
);
}).add;
// Create buffers
buffers = num_samples.collect { Buffer.new };
// Define pattern
trigs = num_samples.collect { |buffer| Array.fill(num_pattern_steps) { \rest } };
spawn_pattern = { |repeats|
var sample_triggering, timing_and_swing, playpos_bumping;
timing_and_swing = Pbind(*[
dur: Prout({
var no_swing_dur = pattern_beats/num_pattern_steps;
(num_pattern_steps/2) do: {
var swing_offset;
swing_offset = current_swing_offset;
(no_swing_dur+swing_offset).yield;
(no_swing_dur-swing_offset).yield;
};
nil.yield;
});
]);
playpos_bumping = Pbind(*[
note: \rest,
tickFunc: Prout({
playpos = 0;
loop {
player.changed(\playpos, playpos);
if (playpos < (num_pattern_steps-1)) { playpos = playpos + 1 } { nil }.yield;
}
})
]);
sample_triggering = buffers.collect { |buffer, i|
Pbind(*[
type: Pseq(event_type, inf),
instrument: 'sampleplayer',
bufnum: buffer,
note: Pseq(trigs[i], 1)
])
};
Ppar(
(sample_triggering ++ playpos_bumping).collect { |pattern| Pchain(pattern, timing_and_swing) },
repeats
);
};
// Transport, pattern editing, tempo and swing functions
play = { |repeats=inf|
player.isPlaying.if {
"Already playing...".inform;
} {
player = if (player.isNil) {
spawn_pattern.(repeats).asEventStreamPlayer;
} {
"Resuming pattern".inform;
player;
};
player.play(TempoClock.default);
};
player
};
stop = {
player.isPlaying.if {
player.stop;
};
};
set_event_type = { |argevent_type|
event_type[0] = argevent_type;
};
clear_pattern = {
num_samples.do { |samplenum|
num_pattern_steps.do { |stepnum|
trig_is_set.(samplenum, stepnum).if { toggle_trig.(samplenum, stepnum) };
};
};
};
toggle_trig = { |samplenum, stepnum|
var trig;
trig = if (trig_is_set.(samplenum, stepnum), \rest, 1);
trigs[samplenum][stepnum] = trig;
trigs.changed(\trig, samplenum, stepnum, trig == 1);
};
trig_is_set = { |samplenum, stepnum|
trigs[samplenum][stepnum] == 1
};
get_playpos = { playpos };
get_tempo = { current_tempo };
adjust_tempo = { |bpm_delta|
set_tempo.(current_tempo + bpm_delta);
};
set_tempo = { |bpm|
current_tempo = tempo_spec.constrain(bpm);
TempoClock.default.tempo_(current_tempo/60);
player.changed('tempo', current_tempo);
};
get_swing_amount = { current_swing_amount };
adjust_swing_amount = { |swing_amount_delta|
set_swing_amount.(current_swing_amount + swing_amount_delta);
};
set_swing_amount = { |swing_amount|
var max_swing_timing_offset;
max_swing_timing_offset = (pattern_beats/num_pattern_steps)/2;
current_swing_amount = swing_amount_spec.constrain(swing_amount);
current_swing_offset = max_swing_timing_offset * current_swing_amount / 100;
player.changed('swing_amount', current_swing_amount);
};
add_trigs_dependant = { |object|
trigs addDependant: object
};
add_player_dependant = { |object|
player addDependant: object
};
load_sample = { |samplenum, path|
buffers[samplenum].allocReadChannel(path, channels: [0]);
};
CmdPeriod.add { player = nil };
// Init defaults
set_tempo.(default_tempo);
set_swing_amount.(default_swing_amount);
// Export functions
~clear_pattern = clear_pattern;
~toggle_trig = toggle_trig;
~trig_is_set = trig_is_set;
~get_playpos = get_playpos;
~get_tempo = get_tempo;
~adjust_tempo = adjust_tempo;
~set_tempo = set_tempo;
~get_tempo_spec = get_tempo_spec;
~get_swing_amount = get_swing_amount;
~adjust_swing_amount = adjust_swing_amount;
~set_swing_amount = set_swing_amount;
~get_swing_amount_spec = get_swing_amount_spec;
~play = play;
~stop = stop;
~add_trigs_dependant = add_trigs_dependant;
~add_player_dependant = add_player_dependant;
~load_sample = load_sample;
~set_event_type = set_event_type;
// Start playing
play.();
)
(
// Section 2: Setup grid and arc control. This uses and depends on the SerialOSCClient library to be installed.
var num_samples = 8, num_pattern_steps = 8;
var tempo_ring_x, swing_amount_ring_x;
var refresh_tempo_and_swing_amount_encoder_rings, update_tempo_encoder_ring, update_swing_amount_encoder_ring;
var update_all_pattern_grid_leds, update_pattern_step_grid_leds, set_playpos_grid_leds;
SerialOSCClient.init(
completionFunc: {
update_all_pattern_grid_leds.();
refresh_tempo_and_swing_amount_encoder_rings.();
};
);
GridKeydef.press(\edit_pattern, { |x, y| ~toggle_trig.(y, x) });
EncDeltadef(\edit_tempo, { |n, delta| ~adjust_tempo.(delta) }, 0);
EncDeltadef(\edit_swing_amount, { |n, delta| ~adjust_swing_amount.(delta) }, 1);
SerialOSCGrid.addDependant { |thechanged, what|
if (what == 'default') { update_all_pattern_grid_leds.() };
};
SerialOSCEnc.addDependant { |thechanged, what|
if (what == 'default') { refresh_tempo_and_swing_amount_encoder_rings.(force_update: true) };
};
~add_trigs_dependant.({ |thechanged, what, samplenum, stepnum, trig_is_set|
SerialOSCGrid.default !? { |grid|
if (stepnum != ~get_playpos.()) {
grid.ledSet(stepnum, samplenum, trig_is_set);
};
};
});
~add_player_dependant.({ |thechanged, what ... args|
switch (what)
{ 'playpos' } {
var previous_playpos;
previous_playpos = (~get_playpos.()-1).wrap(0, num_pattern_steps-1);
update_pattern_step_grid_leds.(previous_playpos);
set_playpos_grid_leds.();
}
{ 'tempo' } { update_tempo_encoder_ring.() }
{ 'swing_amount' } { update_swing_amount_encoder_ring.() };
});
update_all_pattern_grid_leds = {
num_pattern_steps do: { |stepnum| update_pattern_step_grid_leds.(stepnum) };
};
update_pattern_step_grid_leds = { |stepnum|
SerialOSCGrid.default !? { |grid|
num_samples do: { |samplenum|
grid.ledSet(
stepnum,
samplenum,
~trig_is_set.(samplenum, stepnum) or: (stepnum == ~get_playpos.())
);
};
};
};
set_playpos_grid_leds = {
SerialOSCGrid.default !? { |grid|
num_samples do: { |samplenum|
grid.ledSet(
~get_playpos.(),
samplenum,
true
);
};
};
};
refresh_tempo_and_swing_amount_encoder_rings = { |force_update=false|
update_tempo_encoder_ring.(force_update);
update_swing_amount_encoder_ring.(force_update);
};
update_tempo_encoder_ring = { |force_update=false|
var tempo, tempo_spec, old_ring_x, new_ring_x;
tempo = ~get_tempo.();
tempo_spec = ~get_tempo_spec.();
new_ring_x = SerialOSCEnc.ledXSpec.map(tempo_spec.unmap(tempo));
if ((tempo_ring_x != new_ring_x) or: force_update) {
old_ring_x = tempo_ring_x;
SerialOSCEnc.default !? { |enc|
old_ring_x !? { enc.ringSet(0, old_ring_x, 0) };
enc.ringSet(0, new_ring_x, 15 );
};
tempo_ring_x = new_ring_x;
}
};
update_swing_amount_encoder_ring = { |force_update=false|
var swing_amount, swing_amount_spec, old_ring_x, new_ring_x;
swing_amount = ~get_swing_amount.();
swing_amount_spec = ~get_swing_amount_spec.();
new_ring_x = SerialOSCEnc.ledXSpec.map(swing_amount_spec.unmap(swing_amount));
if ((swing_amount_ring_x != new_ring_x) or: force_update) {
old_ring_x = swing_amount_ring_x;
SerialOSCEnc.default !? { |enc|
old_ring_x !? { enc.ringSet(1, old_ring_x, 0) };
enc.ringSet(1, new_ring_x, 15 );
};
swing_amount_ring_x = new_ring_x;
}
};
)
(
// Section 3: Example of loading samples, defining a pattern and setting tempo and swing amount. NOTE: You need
// to change rootpath and sample file names below to something applicable for your machine
var rootpath;
rootpath= "C:/Users/ahorse/Dropbox/Music/Sample Library/Roland Tr-808";
[
"TR-808Kick11.wav",
"TR-808Hat_C02.wav",
"TR-808Clap01.wav",
"TR-808Cow.wav",
"TR-808Snare02.wav",
"TR-808Shaker01.wav",
"TR-808Tom03.wav",
"TR-808Clave.wav"
].do { |filename, i|
~load_sample.(i, rootpath +/+ filename);
};
~clear_pattern.();
~set_tempo.(115);
~set_swing_amount.(40);
~toggle_trig.(0, 0); ~toggle_trig.(0, 4);
~toggle_trig.(1, 1); ~toggle_trig.(1, 3);
~toggle_trig.(2, 4);
~toggle_trig.(4, 4);
~toggle_trig.(5, 4); ~toggle_trig.(5, 5);
~toggle_trig.(6, 6);
~toggle_trig.(7, 2);
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment