Last active
May 13, 2016 19:37
-
-
Save antonhornquist/d35906a90ccd4a872ea8b0e279641145 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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