Skip to content

Instantly share code, notes, and snippets.

@tlubke
Last active March 24, 2019 04:30
Show Gist options
  • Save tlubke/b170fbce7c0a233102a814536e142067 to your computer and use it in GitHub Desktop.
Save tlubke/b170fbce7c0a233102a814536e142067 to your computer and use it in GitHub Desktop.
roda (2.0)
-- roda
-- @tyler
-- a microrhythmic sampler
--
-- inspired by the microrhythms
-- of Samba de Roda.
--
-- load samples via parameters
--
-- DISPLAY
-- -------------------------------------------
-- bpm: | ratio to beat |
-- track 1 # divisions
-- track 2 # divisions
-- track 3 # divisions
-- track 4 # divisions
-- pause/play
--
-- CONTROLS
-- --------------------------------------------
-- key1: ALT
-- key2: change track
-- key3: play/pause
-- *ALT: mute track
--
-- enc1: select division
-- *ALT: change bpm
-- enc2: shift selected division
-- enc3: change # of divisions
-- in selected track [1-9]
--
-- PARAMETERS
-- --------------------------------------------
-- standard 'ack' parameters
--
-- RANDOM MODE
-- ---------------------------------------------
-- randomly change a handful
-- of parameters on each
-- sample triggered.
--
-- In the parameter menu...
--
-- mode 0: none/off
-- mode 1: total random
-- mode 2: step-based random
-- (like Drunk Obj. in Max/MSP)
--
-- each track can be muted
-- so as not to be altered by
-- random mode.
--
-- 0 = affectable
-- 1 = unaffectable
engine.name = 'Ack'
local ack = require 'we/lib/ack'
------------
-- variables
------------
-- clocking variables
position = 0
q_position = 0
running = false
ppq = 192 -- pulses per quarter
track = {{},{},{},{}, divs = {1,2,3,4}}
track_select = 0
div_select = 0
alt = 0
-- init tracks
for i=1, #track do
for j=1, track.divs[i] do
table.insert(track[i], j, (ppq//track.divs[i])*j)
end
end
----------------
-- initilization
----------------
-- FIX ME! set_action with key press?
function init()
-- parameters
params:add_number("preset", "preset:", 1, 128, 1)
params:add_option("file", "save-load", {"save","S<->L","load"}, 2)
params:set_action("file", function(x) if x == 1 then save() elseif x == 3 then load() end end)
params:add_separator()
params:add_number("bpm", "bpm", 15, 400, 60)
params:add_option("ppq", "resolution", {96,192,288,384}, 2)
params:set_action("ppq", function(x) ppq = params:get("ppq") * 96 end)
params:add_number("random_mode", "random mode:", 0, 2, 0)
params:add_number("drunk_step", "mode 2 step size:", 1, 10, 1)
params:add_number("1_mute", "mute track 1:", 0, 1, 0)
params:add_number("2_mute", "mute track 2:", 0, 1, 0)
params:add_number("3_mute", "mute track 3:", 0, 1, 0)
params:add_number("4_mute", "mute track 4:", 0, 1, 0)
params:set_action("1_mute", function(x) mute_groups(1,x) end )
params:set_action("2_mute", function(x) mute_groups(2,x) end )
params:set_action("3_mute", function(x) mute_groups(3,x) end )
params:set_action("4_mute", function(x) mute_groups(4,x) end )
params:add_separator()
ack.add_effects_params()
params:add_separator()
for channel=1, 4 do
ack.add_channel_params(channel)
params:add_separator()
end
params:read("roda.pset")
-- metronome setup
clk = metro.init(count, 60 / (params:get("bpm") * ppq), -1)
random_mute_groups("1-4",0)
redraw()
end
mute = {0,0,0,0}
random_mute = {}
function random_mute_groups(track,x)
if x == 1 then
print("track "..track.." muted")
else
print("track "..track.." unmuted")
end
for i=1, 4 do
if params:get(i.."_mute") == 1 then
random_mute[i] = 1
else
random_mute[i] = 0
end
end
tab.print(random_mute)
end
---------------------------
-- norns control functions
---------------------------
function enc(n,d)
if n == 1 then
div_select = (div_select + d) % track.divs[track_select+1]
if alt == 1 then
params:delta("bpm", d)
end
end
if n == 2 then
track[track_select+1][div_select+1] = util.clamp(track[track_select+1][div_select+1] + d, 1, ppq - 1)
print(track[track_select+1][div_select+1])
end
if n == 3 then
refresh_track(
track_select+1,
util.clamp(track.divs[track_select+1] + d, 1, 9)
)
end
redraw()
end
function key(n,z)
if z == 1 then
if n == 1 then
alt = 1
end
if n == 2 then
track_select = (track_select + 1) % #track
div_select = 0
end
if n == 3 then
if alt == 1 then
mute[track_select+1] = (mute[track_select+1] + 1) % 2
else
if running then
clk:stop()
running = false
else
clk:start()
running = true
end
end
end
else
if n == 1 then
alt = 0
end
end
redraw()
end
------------------
-- active functions
-------------------
function refresh_track(i, divs)
div_select = divs - 1
track[i] = {}
track.divs[i] = divs
for j = 1, track.divs[i] do
table.insert(track[i], j, (ppq//track.divs[i]) * j)
end
end
function simplify(n,d)
while n ~= 1 do
if n/2 == n//2 and d/2 == d//2 then
n = n//2
d = d//2 -- don't need to check d, typically a power of 2.
elseif n/3 == 1 then
n = n//3
d = d//3
return({n,d})
else
return({n,d})
end
end
return({n,d})
end
function count(c)
position = (position + 1) % (ppq + 1)
if position == 0 then position = 1 end
clk.time = 60 / (params:get("bpm") * ppq)
for i=1, #track do
for j=1, #track[i] do
if position / track[i][j] == 1 then
if mute[i] == 0 then
engine.trig(i-1)
end -- random modes affect after trigger
if params:get("random_mode") == 1 then -- mode 1:total random
if params:get(i.."_mute") == 0 then
params:set(i.."_start_pos", math.random())
--params:set(i.."_speed", math.random())
params:set(i.."_pan", math.random(-1,1)*math.random()) -- -1 or 1 * random float, to fit -1 through 1 panning range
params:set(i.."_filter_cutoff", math.random(20,20000))
params:set(i.."_filter_res", math.random())
params:set(i.."_filter_env_atk", math.random())
params:set(i.."_filter_env_rel", math.random())
params:set(i.."_filter_env_mod", math.random())
params:set(i.."_dist", math.random())
end
elseif params:get("random_mode") == 2 then -- mode 2: step-based (like drunk from Max) random
if params:get(i.."_mute") == 0 then
size = params:get("drunk_step")
params:delta(i.."_start_pos", (math.random(-10,10)/100)*size)
--params:delta(i.."_speed", (math.random(-10,10)/100)*size)
params:delta(i.."_pan", (math.random(-10,10)/100)*size) -- -1 or 1 * random float, to fit -1 through 1 panning range
params:delta(i.."_filter_cutoff", math.random(-100*size,100*size))
params:delta(i.."_filter_res", (math.random(-10,10)/100)*size)
params:delta(i.."_filter_env_atk", (math.random(-10,10)/100)*size)
params:delta(i.."_filter_env_rel", (math.random(-10,10)/100)*size)
params:delta(i.."_filter_env_mod", (math.random(-10,10)/100)*size)
params:delta(i.."_dist", (math.random(-10,10)/100)*size)
end
end
end
end
end
end
---------------------------
-- refresh/redraw functions
---------------------------
function redraw()
screen.clear()
screen.aa(0)
screen.level(15)
-- track_select cursor
screen.rect(0, track_select*10 + 15, 1, 3)
screen.fill()
-- divisions
screen.aa(1)
for i=1, #track do
for j=1, track.divs[i] do
if track[i][j] ~= ppq then
screen.move(track[i][j]/(ppq/120), i*10 + 5)
else
screen.move(3, i*10 + 5)
end
screen.line_rel(0,3)
if j == div_select+1 and i == track_select+1 then
screen.line_rel(0,-6)
end
screen.stroke()
-- division length display
screen.move(123, i*10 + 9)
screen.text(track.divs[i])
end
end
screen.aa(0)
-- param display
screen.move(1,5)
screen.text("bpm:"..params:get("bpm"))
-- pause/play icon
if not running then
screen.rect(123,57,2,6)
screen.rect(126,57,2,6)
screen.fill()
else
screen.move(123,57)
screen.line_rel(6,3)
screen.line_rel(-6,3)
screen.fill()
end
-- track mute toggle
for i=1, #track do
screen.move(4 + (i-1)*20, 60)
screen.text_right(i)
screen.rect(8 + (i-1)*20, 54, 8, 8)
screen.move(9 + (i-1)*20, 60)
if mute[i] == 0 then screen.level(1) end
screen.text("M")
screen.level(15)
screen.stroke()
end
screen.level(12)
-- tracks
for i=1, 4 do
screen.rect(2, i*10+5, 118,4)
screen.stroke()
end
screen.level(1)
-- ratio display
screen.move(128,5)
screen.text_right(
simplify(track[track_select+1][div_select+1],ppq)[1]
.."/"..
simplify(track[track_select+1][div_select+1],ppq)[2]
)
screen.stroke()
screen.update()
end
function save()
tab.save(track, '/home/we/dust/we/data/roda-'.. params:get("preset") ..'.data')
print("SAVE COMPLETE", params:get("preset"))
end
function load()
track = tab.load('/home/we/dust/we/data/roda-'.. params:get("preset") ..'.data')
print("LOAD COMPLETE", params:get("preset"))
redraw()
end
@rross101
Copy link

rross101 commented Mar 16, 2019

Can't get this version playing. Probably my own fault. However I do get an error when turning on mute track:

lua: /home/we/dust/roda/roda.lua:112: attempt to call a nil value (global 'mute_groups')
stack traceback:
	/home/we/dust/roda/roda.lua:112: in field 'action'
	/home/we/norns/lua/core/params/number.lua:44: in function 'core/params/number.bang'
	/home/we/norns/lua/core/params/number.lua:31: in function 'core/params/number.set'
	/home/we/norns/lua/core/params/number.lua:36: in function 'core/params/number.delta'
	/home/we/norns/lua/core/paramset.lua:166: in function 'core/paramset.delta'
	/home/we/norns/lua/core/menu.lua:550: in function 'fileselect.enc_restore'
	/home/we/norns/lua/core/menu.lua:142: in function 'core/encoders.callback'
	/home/we/norns/lua/core/encoders.lua:57: in function 'core/encoders.process'
>>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment