Skip to content

Instantly share code, notes, and snippets.

@okyeron
Last active September 1, 2018 05:22
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 okyeron/c2386ba360ea0a66b2eceadf38164315 to your computer and use it in GitHub Desktop.
Save okyeron/c2386ba360ea0a66b2eceadf38164315 to your computer and use it in GitHub Desktop.
breakbeat step sequencer
-- break_step v.014
-- sample based step sequencer with preset patterns from well known breakbeats
-- based-on/extended-from jah/step.lua and jah/stepmod.lua
-- controlled by grid
--
-- requires drumpattrns.txt -- edit path below
--
-- key2 = stop sequencer
-- key3 = play sequencer
-- enc2 = tempo
-- enc3 = pattern select
--
-- swing amount is in PARAMETERS
--
-- grid = edit trigs
--
-- select drum kit (606,808,909) from parameters
--
-- change the data directory path here as needed
local file = data_dir .. "okyeron/drumpattrns.txt"
engine.name = 'Ack'
local gr = grid.connect()
local ControlSpec = require 'controlspec'
local Metro = require 'metro'
local Ack = require 'jah/ack'
local TRIG_LEVEL = 15
local PLAYPOS_LEVEL = 7
local CLEAR_LEVEL = 0
local tempo_spec = ControlSpec.new(20, 300, ControlSpec.WARP_LIN, 0, 120, "BPM")
local swing_amount_spec = ControlSpec.new(0, 100, ControlSpec.WARP_LIN, 0, 0, "%")
local maxwidth = 16
local height = 8
local playing = false
local queued_playpos
local playpos = -1
local timer
local key3down
local ppqn = 24
local ticks
local ticks_to_next
local odd_ppqn
local even_ppqn
local trigger_indicators = {}
local grid_available
local locks = {}
local prev_locks
local file = data_dir .. "okyeron/drumpattrns.txt"
local big_patterns = {}
local sub_patterns ={}
local drumkits = {
{"606-BD","606-SD","606-CH","606-OH","606-CY","606-HT","606-LT"},
{"808-BD","808-SD","808-CH","808-OH","808-CY","808-CP","808-MA","808-CB"},
{"909-BD","909-SD","909-CH","909-OH","909-CY","909-CP","909-RS","909-RC"}
}
drumkits[1].key = "606"
drumkits[2].key = "808"
drumkits[3].key = "909"
local p_num = 1
local p_idx = 1
local trigs = {}
-- trig/lock functions
local function set_lock(x, y, value) -- TODO: param locks
locks[y*maxwidth+x] = value
end
local function trig_is_locked(x, y) -- TODO: param locks
return locks[y*maxwidth+x]
end
local function get_lock(x, y) -- TODO: param locks
return locks[y*maxwidth+x]
end
-- pattern save hack (start)
local function set_bit(r, index)
return r | 1 << index
end
local function clear_bit(r, index)
return r & ~(1<<index)
end
-- pattern save hack (fin)
local function set_trig(x, y, value)
trigs[y*maxwidth+x] = value
if not value then
set_lock(x, y, nil)
end
end
local function trig_is_set(x, y)
return trigs[y*maxwidth+x]
end
-- utility functions
local function split_str(inputstr, sep)
if sep == nil then
sep = "%s"
end
local t={} ; i=1
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
t[i] = str
i = i + 1
end
return t
end
local function is_even(number)
return number % 2 == 0
end
local function tablelength(T)
local count = 0
for _ in pairs(T) do count = count + 1 end
return count
end
-- file i/o functions
-- see if the file exists
local function file_exists(file)
local f = io.open(file, "r")
if f then f:close() end
return f ~= nil
end
-- get all lines from a file, returns an empty
-- list/table if the file does not exist
local function lines_from(file)
if file_exists(file) then print ("reading text file to table") end
lines = {}
for line in io.lines(file) do
lines[#lines + 1] = line
end
return lines
end
-- grid functions
local function refresh_grid_button(x, y, refresh)
if params:get("last row cuts") == 2 and y == 8 then
if x-1 == playpos then
gr.led(x, y, PLAYPOS_LEVEL)
else
gr.led(x, y, CLEAR_LEVEL)
end
else
if trig_is_set(x, y) then
gr.led(x, y, TRIG_LEVEL)
elseif x-1 == playpos then
gr.led(x, y, PLAYPOS_LEVEL)
else
gr.led(x, y, CLEAR_LEVEL)
end
end
if refresh then
gr.refresh()
end
end
-- pattern save hack (start)
local function restore_row_trigs(paramvalue, y)
for x=1,16 do
set_trig(x, y, paramvalue & (1<<x) ~= 0)
refresh_grid_button(x, y, false)
end
gr.refresh()
end
-- pattern save hack (fin)
local function refresh_grid_column(x, refresh)
for y=1,height do
refresh_grid_button(x, y, false)
end
if refresh then
gr.refresh()
end
end
local function refresh_grid()
gr.all(0)
for x=1,maxwidth do
refresh_grid_column(x, false)
end
gr.refresh()
end
-- pattern load
local function patternload(pid)
trigs = {} -- reset trigs
--print(big_patterns[pid])
for y=1,tablelength(big_patterns[pid])-1 do
for x=1,16 do
if (tonumber(big_patterns[pid][y][2][x])==1) then
set_trig(x,y,true)
refresh_grid_button(x, y, false)
end
end
end
refresh_grid()
end
local function drumkitload(kitid)
for y=1,tablelength(drumkits[kitid])-1 do
params:set(y..": sample", "/home/we/dust/audio/common/".. drumkits[kitid].key .."/"..drumkits[kitid][y]..".wav")
end
end
local function tick()
ticks = (ticks or -1) + 1
if (not ticks_to_next) or ticks_to_next == 0 then
local previous_playpos = playpos
if queued_playpos then
playpos = queued_playpos
queued_playpos = nil
elseif params:get("grid width") == 1 then
playpos = (playpos + 1) % 8
else
playpos = (playpos + 1) % 16
end
local new_prev_locks = {}
local ts = {}
for y=1,8 do
if trig_is_set(playpos+1, y) and not (params:get("last row cuts") == 2 and y == 8) then
ts[y] = 1
else
ts[y] = 0
end
if trig_is_locked(playpos+1, y) and not (params:get("last row cuts") == 2 and y == 8) then
engine.speed(speed_spec:map(get_lock(playpos+1, y)))
new_prev_locks[y] = true
else
if prev_locks and prev_locks[y] then
engine.speed(params:get(y.."speed"))
end
end
end
prev_locks = new_prev_locks
engine.multiTrig(ts[1], ts[2], ts[3], ts[4], ts[5], ts[6], ts[7], ts[8])
if previous_playpos ~= -1 then
refresh_grid_column(previous_playpos+1)
end
if playpos ~= -1 then
refresh_grid_column(playpos+1)
end
gr.refresh()
if is_even(playpos) then
ticks_to_next = even_ppqn
else
ticks_to_next = odd_ppqn
end
redraw()
else
ticks_to_next = ticks_to_next - 1
end
end
local function update_metro_time()
timer.time = 60/params:get("tempo")/ppqn/params:get("beats per pattern")
end
local function update_swing(swing_amount)
local swing_ppqn = ppqn*swing_amount/100*0.75
even_ppqn = util.round(ppqn+swing_ppqn)
odd_ppqn = util.round(ppqn-swing_ppqn)
end
gr.event = function(x,y,state) -- grid key events
if state == 1 then
if params:get("last row cuts") == 2 and y == 8 then
queued_playpos = x-1
else
if trig_is_set(x, y) then
set_trig(x, y, false)
refresh_grid_button(x, y, true)
else
set_trig(x, y, true)
refresh_grid_button(x, y, true)
end
end
gr.refresh()
end
redraw()
end
-- INIT
function init()
gr.all(0)
local pid = 1 -- default pattern
local drumkit_id = 2 -- default drumkit
for x=1,maxwidth do
for y=1,height do
-- set_trig(x, y, false)
end
end
timer = Metro.alloc()
timer.callback = tick
--params:read("okyeron/break_step.pset")
--print ("reading pset")
-- read pattern file
local lines = lines_from(file)
for k,v in pairs(lines) do
if (v == "!") then break end
if (v ~= "") then
split_line1 = split_str(v, "|")
if (split_line1[1] == "Pattern") then
pttrn_name = split_line1[2]
--big_patterns[p_num] = {key = pttrn_name}
p_idx = 1
else
split_line2 = split_str(split_line1[2], ",")
sub_patterns[#sub_patterns+1] = {split_line1[1], split_line2}
p_idx=p_idx+1
end
else
-- table.insert(big_patterns, {key = pttrn_name, value= {sub_patterns}})
big_patterns[p_num] = sub_patterns
big_patterns[p_num].key = pttrn_name
sub_patterns = {}
p_num = p_num+1
end
end
--some debug to know what is what in the patterns table
--tab.print(big_patterns[pid])
--print (tablelength(big_patterns))
--print (big_patterns[1].key) -- pattern name
--print (big_patterns[pid][1][1] .. " - instrument") -- instrument
--print (big_patterns[pid][1][2][2] .. " - step") -- [set][y][2][x]
-- setup pattern name params
params:add_number("pattern select",1,tablelength(big_patterns)-1,1)
params:set_action("pattern select", function(n) patternload(n) end)
params:set("pattern select", pid)
params:add_option("drumkit",{"606", "808", "909"},drumkit_id)
params:set_action("drumkit", function(n) drumkitload(n) end)
--tab.print(drumkits)
--print(drumkit_id)
--print (drumkits[drumkit_id].key)
-- original step param setup
params:add_option("grid width", {"8", "16"}, 2) -- TODO: should now be possible to infer from grid metadata(?)
params:set_action("grid width", function(value) update_metro_time() end)
params:add_option("last row cuts", {"no", "yes"}, 1)
params:set_action("last row cuts", function(value)
last_row_cuts = (value == 2)
refresh_grid()
end)
params:add_number("beats per pattern", 1, 8, 4)
params:set_action("beats per pattern", function(value) update_metro_time() end)
params:add_control("tempo", tempo_spec)
params:set_action("tempo", function(bpm) update_metro_time() end)
update_metro_time()
params:add_control("swing amount", swing_amount_spec)
params:set_action("swing amount", update_swing)
params:add_separator()
Ack.add_params()
patternload(pid)
params:bang()
-- pattern save hack (start)
-- pattern save hack (fin)
playing = true
timer:start()
gr.refresh()
cleanup()
end
function cleanup()
-- pattern save hack (start)
params:write("okyeron/break_step.pset")
print ("wrote pset")
-- pattern save hack (fin)
end
function enc(n, delta)
if n == 1 then
mix:delta("output", delta)
elseif n == 2 then
params:delta("tempo", delta)
elseif n == 3 then
params:delta("pattern select", delta)
-- params:delta("swing amount", delta)
end
redraw()
end
function key(n, z)
if n == 2 and z == 1 then
if playing == false then
playpos = -1
queued_playpos = 0
redraw()
refresh_grid()
else
playing = false
timer:stop()
end
elseif n == 3 and z == 1 then
if z == 1 then
playing = true
timer:start()
key3down = true
else
key3down = false
end
end
redraw()
end
function redraw() -- display redraw
screen.font_size(8)
screen.clear()
screen.level(15)
screen.move(10,30)
if playing then
screen.level(3)
screen.text("[] stop")
else
screen.level(15)
screen.text("[] stopped")
end
--[[
screen.level(3)
screen.move(50,30)
if playing then
screen.text(" > ")
else
screen.text(" < ")
end
]]
screen.font_size(8)
screen.move(70,30)
if playing then
screen.level(15)
screen.text("|> playing")
screen.text(" "..playpos+1)
else
screen.level(3)
screen.text("|> play")
end
screen.level(15)
screen.move(10,50)
screen.text(params:string("tempo"))
screen.move(70,50)
screen.text(big_patterns[params:get("pattern select")].key)
screen.level(3)
screen.move(10,60)
screen.text("tempo")
screen.move(70,60)
screen.text("pattern")
screen.update()
end
Pattern|BillieJean
808-BD.wav|1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0
808-SD.wav|0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0
808-CH.wav|1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0
808-OH.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
808-CY.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
808-CP.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Pattern|ColdSweat
808-BD.wav|1,0,1,0,0,0,0,0,1,0,1,0,0,0,0,0
808-SD.wav|0,0,0,0,1,0,0,1,0,0,0,0,1,0,0,1
808-CH.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
808-OH.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
808-CY.wav|1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0
808-CP.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Pattern|ImpeachThePresident
808-BD.wav|1,0,0,0,0,0,0,1,1,0,0,0,0,0,1,0
808-SD.wav|0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0
808-CH.wav|1,0,1,0,1,0,1,1,1,0,0,0,1,0,1,0
808-OH.wav|0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0
808-CY.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
808-CP.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Pattern|WhenTheLeveeBreaks
808-BD.wav|1,1,0,0,0,0,0,1,0,0,1,1,0,0,0,0
808-SD.wav|0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0
808-CH.wav|1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0
808-OH.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
808-CY.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
808-CP.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Pattern|TheFunkyDrummer
808-BD.wav|1,0,1,0,0,0,1,0,0,0,1,0,0,1,0,0
808-SD.wav|0,0,0,0,1,0,0,1,0,1,0,1,1,0,0,1
808-CH.wav|1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,1
808-OH.wav|0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0
808-CY.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
808-CP.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Pattern|WalkThisWay
808-BD.wav|1,0,0,0,0,0,0,1,1,0,1,0,0,0,0,0
808-SD.wav|0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0
808-CH.wav|0,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0
808-OH.wav|1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
808-CY.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
808-CP.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Pattern|ItsaNewDay
808-BD.wav|1,0,1,0,0,0,0,0,0,0,1,1,0,0,0,1
808-SD.wav|0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0
808-CH.wav|1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0
808-OH.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
808-CY.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
808-CP.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Pattern|PapaWasToo
808-BD.wav|1,0,0,0,0,0,0,1,1,0,1,0,0,0,0,1
808-SD.wav|0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0
808-CH.wav|0,0,0,0,1,0,0,0,1,0,1,0,1,0,1,1
808-OH.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
808-CY.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
808-CP.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
808-MA.wav|0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0
Pattern|TheBigBeat
808-BD.wav|1,0,0,1,0,0,1,0,1,0,0,0,0,0,0,0
808-SD.wav|0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0
808-CH.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
808-OH.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
808-CY.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
808-CP.wav|0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0
Pattern|AshleysRoachclip
808-BD.wav|1,0,1,0,0,0,1,0,0,1,1,0,0,0,0,0
808-SD.wav|0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0
808-CH.wav|1,0,1,0,1,0,1,0,1,0,0,0,1,0,1,0
808-OH.wav|0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0
808-CY.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
808-CP.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
808-MA.wav|1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
Pattern|SyntheticSubstitution-part1
808-BD.wav|1,0,1,0,0,0,0,1,0,1,1,1,0,0,0,1
808-SD.wav|0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0
808-CH.wav|1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0
808-OH.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
808-CY.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
808-CP.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Pattern|SyntheticSubstitution-part2
808-BD.wav|1,0,1,0,0,0,0,1,0,1,1,1,0,0,0,1
808-SD.wav|0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0
808-CH.wav|1,0,0,0,1,0,1,0,1,0,1,0,1,0,1,0
808-OH.wav|0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0
808-CY.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
808-CP.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Pattern|Amen-Brother-part1
808-BD.wav|1,0,1,0,0,0,0,0,0,0,1,1,0,0,0,0
808-SD.wav|0,0,0,0,1,0,0,1,0,1,0,0,1,0,0,1
808-CH.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
808-OH.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
808-CY.wav|1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0
808-CP.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Pattern|Amen-Brother-part2
808-BD.wav|1,0,1,0,0,0,0,0,0,0,1,1,0,0,0,0
808-SD.wav|0,0,0,0,1,0,0,1,0,1,0,0,1,0,0,1
808-CH.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
808-OH.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
808-CY.wav|1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0
808-CP.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Pattern|Amen-Brother-part3
808-BD.wav|1,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0
808-SD.wav|0,0,0,0,1,0,0,1,0,1,0,0,0,0,1,0
808-CH.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
808-OH.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
808-CY.wav|1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0
808-CP.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Pattern|Amen-Brother-part4
808-BD.wav|0,0,1,1,0,0,0,0,0,0,1,0,0,0,0,0
808-SD.wav|0,1,0,0,1,0,0,1,0,1,0,0,0,0,1,0
808-CH.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
808-OH.wav|0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0
808-CY.wav|1,0,1,0,1,0,1,0,1,0,0,0,1,0,1,0
808-CP.wav|0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment