| // 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