Skip to content

Instantly share code, notes, and snippets.

@dndrks
Created July 19, 2020 16:44
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 dndrks/946abb619bbce2761936a612f3c83fcf to your computer and use it in GitHub Desktop.
Save dndrks/946abb619bbce2761936a612f3c83fcf to your computer and use it in GitHub Desktop.
--
-- PATCHWORK (v2.0)
--
-- Dual sequencer for
-- norns, grid + crow
-- @olivier
-- llllllll.co/t/patchwork/28800
--
-- See full documentation
-- in library post on lines
--
--
local ControlSpec = require "controlspec"
local Formatters = require "formatters"
local pages = {"EDIT", "COMMANDS"}
local pageNum = 1
local scaleGroup = 1
local scale_names = {}
local scaleLength = 8
local activeSeq = 0
local position = {1,1}
local edit = {1,1}
seqStart = {}
seqStart["A"] = 1
seqStart["B"] = 1
seqEnd = {}
seqEnd["A"] = 16
seqEnd["B"] = 16
--[[
local seqStart_A = 1
local seqStart_B = 1
local seqEnd_A = 16
local seqEnd_B = 16
--]]
local length = {16,16}
local noteSel = {1,1}
local direction = {0,0}
local helpKey = 1
local keydown = {0,0,0}
local gridMode = {0,0}
local midi_out_device
local midi_out_channel_A
local midi_out_channel_B
local pattern = {
{},
{}
}
active_notes = {
{},
{},
}
g = grid.connect()
local music = require 'musicutil'
local offset = {0,0}
local notes = {}
local step = {
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}
}
local rests = {
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
}
local clk_mod = {
{1, "1/8"},
{2, "1/4"},
{4, "1/2"},
{6, "3/4"},
{8, "none"},
{12, "3/2"},
{16, "2/1"},
{24, "3/1"},
{32, "4/1"},
}
function set_outs(target,x)
if x == 1 then
crow.output[2].action = "pulse(.025,5,1)"
crow.ii.jf.mode(0)
elseif x == 2 then
crow.output[4].action = "pulse(.025,5,1)"
crow.ii.jf.mode(0)
elseif x == 3 then
crow.ii.pullup(true)
crow.ii.jf.mode(1)
end
end
function set_out_A(x)
if x == 1 then
crow.output[2].action = "pulse(.025,5,1)"
crow.ii.jf.mode(0)
elseif x == 2 then
crow.output[4].action = "pulse(.025,5,1)"
crow.ii.jf.mode(0)
elseif x == 3 then
crow.ii.pullup(true)
crow.ii.jf.mode(1)
end
end
function set_out_B(x)
if x == 1 then
crow.output[2].action = "pulse(.025,5,1)"
crow.ii.jf.mode(0)
elseif x == 2 then
crow.output[4].action = "pulse(.025,5,1)"
crow.ii.jf.mode(0)
elseif x == 3 then
crow.ii.pullup(true)
crow.ii.jf.mode(1)
end
end
-- CLOCK SETUP
function bang(x)
while true do
local chance = math.random(0,100)
local seq = x == 1 and "a" or "b"
clock.sync((clock.get_tempo()/params:get("bpm_"..seq)/(clk_mod[params:get("mod_"..seq)][1]/8)))
if chance <= params:get("prob_"..seq) then
count(x)
end
end
end
function bang_a()
while true do
local chance_a = math.random(0,100)
clock.sync((clock.get_tempo()/params:get("bpm_a")/(clk_mod[params:get("mod_a")][1]/8)))
if chance_a <= params:get("prob_a") then
--count_A()
count(1)
end
end
end
function bang_b()
while true do
local chance_b = math.random(0,100)
clock.sync((clock.get_tempo()/params:get("bpm_b")/(clk_mod[params:get("mod_b")][1]/8)))
if chance_b <= params:get("prob_b") then
--count_B()
count(2)
end
end
end
function set_clk_src(x)
if x == 1 then
crow.input[1].mode("change", 1.0, 0.25, "rising")
crow.input[2].mode("change", 1.0, 0.25, "rising")
clock.cancel(clk[1])
clock.cancel(clk[2])
elseif x == 2 then
crow.input[1].mode('none')
crow.input[2].mode('none')
clk[1] = clock.run(bang,1)
clk[2] = clock.run(bang,2)
end
end
-- COMMANDS
function build_scale()
notes = music.generate_scale_of_length(params:get("root_note")-60, params:get("scale_mode"), scaleLength)
for i = 1, 7 do
table.insert(notes, notes[i])
end
end
-- A SEQUENCE COMMANDS
function newPattern_A(a,b)
for i = a,b do
pattern[1][i] = math.random(scaleLength)
end
end
function sync_A() position[2] = position[1]; direction[2] = direction[1] end
function offsetDec_A()
if params:get("output_a") ~= 6 then
offset[1] = util.clamp(offset[1]-1,-1,1)
else
offset[1] = -12
end
end
function offsetInc_A()
if params:get("output_a") ~= 6 then
offset[1] = util.clamp(offset[1]+1,-1,1)
else
offset[1] = 12
end
end
function newNote_A() pattern[1][position[1]] = math.random(scaleLength) end
function posRand_A() position[1] = math.random(seqStart["A"],seqEnd["A"]) end
function direction_A() direction[1] = math.random(0,1) end
function rest_A() end
-- B SEQUENCE COMMANDS
function newPattern_B(a,b)
for i = a,b do
pattern[2][i] = math.random(scaleLength)
end
end
function sync_B() position[1] = position[2]; direction[1] = direction[2] end
function offsetDec_B()
if params:get("output_b") ~= 6 then
offset[2] = util.clamp(offset[2]-1,-1,1)
else
offset[2] = -12
end
end
function offsetInc_B()
if params:get("output_b") ~= 6 then
offset[2] = util.clamp(offset[2]+1,-1,1)
else
offset[2] = 12
end
end
function newNote_B() pattern[2][position[2]] = math.random(scaleLength) end
function posRand_B() position[2] = math.random(seqStart["B"],seqEnd["B"]) end
function direction_B() direction[2] = math.random(0,1) end
function rest_B() end
commands = 8
act = {
{offsetDec_A, offsetInc_A, newNote_A, rest_A, direction_A, posRand_A, sync_A, newPattern_A},
{offsetDec_B, offsetInc_B, newNote_B, rest_B, direction_B, posRand_B, sync_B, newPattern_B}
}
label = {"-", "+", "N", "*", "D", "?", "1", "P"}
description = {"Shift down an octave", "Shift up an octave", "New note", "Mute note", "Random direction", "Random step position", "Sync sequences", "Create new pattern"}
function all_notes_off(target)
for _, a in pairs(active_notes[target]) do
if target == 1 then
midi_out_device:note_off(a, nil, params:get("midi_out_channel_A"))
elseif target == 2 then
midi_out_device:note_off(a, nil, params:get("midi_out_channel_B"))
end
end
active_notes[target] = {}
end
function init()
-- crow initialization
crow.init()
crow.clear()
crow.reset()
crow.output[2].action = "pulse(.025,5,1)"
crow.output[4].action = "pulse(.025,5,1)"
-- midi initialization
midi_out_device = midi.connect(1)
-- encoder 1 sensitivity
norns.enc.sens(1,5)
-- clocks
clk = {}
sync_A()
for i = 1, #music.SCALES do
table.insert(scale_names, string.lower(music.SCALES[i].name))
end
-- params
params:set("clock_tempo",120)
params:add_group("PATCHWORK", 16)
params:add_option("output_a", "output (A)", {"crow 1+2", "crow 3+4", "jf.vox 1", "jf.vox 2", "jf.note", "midi"}, 1)
params:set_action("output_a", function(x) set_out_A(x) end)
params:add_option("output_b", "output (B)", {"crow 1+2", "crow 3+4", "jf.vox 1", "jf.vox 2", "jf.note", "midi"}, 2)
params:set_action("output_b", function(x) set_out_B(x) end)
params:add{type = "option", id = "scale_mode", name = "scale",
options = scale_names, default = 12,
action = function() build_scale() end}
params:add{type = "number", id = "root_note", name = "root note",
min = 0, max = 127, default = 60, formatter = function(param) return music.note_num_to_name(param:get(), true) end,
action = function() build_scale() end}
params:add_separator("Clocking")
params:add_option("clock_source", "clock source", {"crow","norns"}, 1)
params:set_action("clock_source", function(x) set_clk_src(x) end)
params:add{type = "number", id = "bpm_a", name = "BPM (A)",
min = 20, max = 240, default = 120,
action = function(x) end}
params:add_option("mod_a", "Div/mult (A)", {
clk_mod[1][2],
clk_mod[2][2],
clk_mod[3][2],
clk_mod[4][2],
clk_mod[5][2],
clk_mod[6][2],
clk_mod[7][2],
clk_mod[8][2],
clk_mod[9][2],
}, 5)
params:add{type = "number", id = "prob_a", name = "Probability (A)",
min = 0, max = 100, default = 100,
action = function(value) end}
params:add{type = "number", id = "bpm_b", name = "BPM (B)",
min = 20, max = 240, default = 120,
action = function(value) end}
params:add_option("mod_b", "Div/mult (B)", {
clk_mod[1][2],
clk_mod[2][2],
clk_mod[3][2],
clk_mod[4][2],
clk_mod[5][2],
clk_mod[6][2],
clk_mod[7][2],
clk_mod[8][2],
clk_mod[9][2],
}, 5)
params:add{type = "number", id = "prob_b", name = "Probability (B)",
min = 0, max = 100, default = 100,
action = function(value) end}
params:add_separator("Midi")
params:add{type = "number", id = "midi_out_device", name = "midi out device",
min = 1, max = 4, default = 1,
action = function(value) midi_out_device = midi.connect(value) end}
params:add{type = "number", id = "midi_out_channel_A", name = "midi out channel (A)",
min = 1, max = 16, default = 1,
action = function(value)
all_notes_off(1)
for i = 1,127 do
midi_out_device:note_off(i, nil, value-1)
midi_out_device:note_off(i, nil, value+1)
end
midi_out_channel_A = value
end}
params:add{type = "number", id = "midi_out_channel_B", name = "midi out channel (B)",
min = 1, max = 16, default = 2,
action = function(value)
all_notes_off(2)
midi_out_channel_B = value
end}
-- REDRAW CLOCK
clk = metro.init(intclk, 0.05, -1)
clk:start()
-- CREATE NEW PATTERNS
newPattern_A(seqStart["A"],seqEnd["A"])
newPattern_B(seqStart["B"],seqEnd["B"])
build_scale()
-- CROW SETUP
-- Inputs
--crow.input[1].change = count(1)
crow.input[1].change = count,1
crow.input[1].mode("change", 1.0, 0.25, "rising")
crow.input[2].change = count,2
crow.input[2].mode("change", 1.0, 0.25, "rising")
end
function count(x)
local Start = seqStart[x == 1 and "A" or "B"]
local End = seqEnd[x == 1 and "A" or "B"]
if direction[x] == 0 then
position[x] = (position[x]-Start+1) % (End-Start+1) + Start
else
position[x] = (position[x]-Start-1) % (End-Start+1) + Start
end
act[x][step[x][position[x]]](Start,End)
if act[x][step[x][position[x]]] ~= act[x][4] then
rests[x][position[x]-1] = 0
local output_param = params:get(x == 1 and "output_a" or "output_b")
if output_param ~= 6 then
all_notes_off(x)
end
if output_param == 1 then
crow.output[1].volts = notes[pattern[x][position[x]]]/12 + offset[x]
crow.output[2].execute()
elseif output_param == 2 then
crow.output[3].volts = notes[pattern[x][position[x]]]/12 + offset[x]
crow.output[4].execute()
elseif output_param == 3 then
crow.ii.jf.play_voice(1,notes[pattern[x][position[x]]]/12 + offset[x],9)
elseif output_param == 4 then
crow.ii.jf.play_voice(2,notes[pattern[x][position[x]]]/12 + offset[x],9)
elseif output_param == 5 then
crow.ii.jf.play_note(notes[pattern[x][position[x]]]/12 + offset[x],9)
elseif output_param == 6 then
all_notes_off(x)
midi_out_device:note_on((notes[pattern[x][position[x]]] + 60) + offset[x],127,params:get(x == 1 and "midi_out_channel_A" or "midi_out_channel_B"))
table.insert(active_notes[x], (notes[pattern[x][position[x]]] + 60) + offset[x])
end
else
rests[x][position[x]-1] = 1
end
redraw()
end
-- GRID FUNCTIONS
g.key = function(x,y,z)
if z == 1 then
if keydown[1] == 0 then
if pageNum == 1 then
if activeSeq == 0 then
if gridMode[1] == 0 then
pattern[1][x] = 9-y
else
if y > 0 then
step[1][x] = 9-y
end
end
else
if gridMode[2] == 0 then
pattern[2][x] = 9-y
else
if y > 0 then
step[2][x] = 9-y
end
end
end
else
helpKey = (1+8) - y
print(helpKey)
end
else
if activeSeq == 0 then
end
end
end
end
function intclk()
for i=1,2 do
if act[i][step[i][position[i]]] == act[i][7] then
position[1] = position[2]
end
end
if pageNum == 1 then
if activeSeq == 0 then
for i=1,16 do
if gridMode[1] == 0 then
g:led(i,9-pattern[1][i],2)
for i=seqStart["A"],seqEnd["A"] do
g:led(i,9-pattern[1][i],i==position[1] and 15 or 8)
end
else
g:led(i,9-step[1][i],i==position[1] and 15 or 8)
end
end
g:refresh()
else
g:all(0)
end
if activeSeq == 1 then
for i=1,16 do
if gridMode[2] == 0 then
g:led(i,9-pattern[2][i],2)
for i=seqStart["B"],seqEnd["B"] do
g:led(i,9-pattern[2][i],i==position[2] and 15 or 8)
end
else
g:led(i,9-step[2][i],i==position[2] and 15 or 8)
end
end
g:refresh()
else
g:all(0)
end
elseif pageNum == 2 then
g:all(0)
for i=1,8 do
g:led(((1+8)-i)+4,i,i==(1+8)-helpKey and 15 or 4)
end
g:refresh()
end
g:all(0)
redraw()
end
-- SCREEN
function redraw()
screen.clear()
if pageNum == 1 then
draw_info()
draw_comm_rows()
draw_seq()
else
drawHelp()
end
screen.update()
end
function drawHelp()
for i=1,#label do
if helpKey == i then
screen.level(15)
else
screen.level(4)
end
screen.move(i*8+25,25)
screen.text(label[i])
end
screen.level(2)
screen.move(33,32)
screen.line(93,32)
screen.stroke()
for i=1,#description do
screen.level(2)
screen.move(64,45)
screen.text_center(description[helpKey])
end
end
function draw_info()
if activeSeq == 0 then
screen.move(11,57)
screen.level(15)
screen.text("A")
screen.level(2)
screen.move(23,57)
screen.text(params:string("output_a"))
screen.rect(18,54,2,2)
screen.fill()
else
screen.move(11,57)
screen.level(15)
screen.text("B")
screen.level(2)
screen.move(23,57)
screen.text(params:string("output_b"))
screen.rect(18,54,2,2)
screen.fill()
end
--
screen.level(2)
screen.move(110,60)
screen.rect(108,52,2,2)
screen.rect(111,52,2,2)
screen.rect(108,55,2,2)
screen.rect(111,55,2,2)
screen.fill()
screen.move(120,57)
screen.level(5)
if gridMode[1] and gridMode[2] == 0 then
screen.text_right("N")
elseif gridMode[1] and gridMode[2] == 1 then
screen.text_right("C")
end
end
function draw_comm_rows()
-- SEQUENCE A
if activeSeq == 0 then
screen.level(15)
else
screen.level(0)
end
screen.move(4,20)
screen.text(">")
for i=1,#step[1] do
if activeSeq == 0 then
screen.level((i == edit[1]) and 15 or 2)
else
screen.level(2)
end
screen.move(i*7+4,20)
screen.text(label[step[1][i]])
end
-- SEQUENCE B
if activeSeq == 1 then
screen.level(15)
else
screen.level(0)
end
screen.move(4,42)
screen.text(">")
for i=1,#step[2] do
if activeSeq == 1 then
screen.level((i == edit[2]) and 15 or 2)
else
screen.level(2)
end
screen.move(i*7+4,42)
screen.text(label[step[2][i]])
end
screen.level(2)
screen.move(10,47)
screen.line(120,47)
screen.stroke()
end
function draw_seq()
-- SEQUENCE A
for i=seqStart["A"],seqEnd["A"] do
if i == position[1] then
screen.level(15)
else
if rests[1][i-1] == 1 then
screen.level(0)
else
screen.level(4)
end
end
screen.rect(i*7+4,8,3,3)
screen.fill()
end
-- SEQUENCE B
for i=seqStart["B"],seqEnd["B"] do
if i == position[2] then
screen.level(15)
else
if rests[2][i-1] == 1 then
screen.level(0)
else
screen.level(4)
end
end
screen.rect(i*7+4,30,3,3)
screen.fill()
end
screen.level(2)
screen.move(10,25)
screen.line(120,25)
screen.stroke()
end
local gmode = 1
function enc(n,d)
if n == 1 then
if keydown[1] == 0 then
gmode = util.clamp(gmode+d,0,1)
for i=1,2 do
gridMode[i] = gmode
end
print(gmode)
end
elseif n == 2 then
if activeSeq == 0 then
if keydown[2] == 0 then
edit[1] = util.clamp(edit[1]+d,1,length[1])
else
seqStart["A"] = util.clamp(seqStart["A"]+d,1,seqEnd["A"]-1)
end
else
if keydown[2] == 0 then
edit[2] = util.clamp(edit[2]+d,1,length[2])
else
seqStart["B"] = util.clamp(seqStart["B"]+d,1,seqEnd["B"]-1)
end
end
elseif n == 3 then
if activeSeq == 0 then
if keydown[2] == 0 then
step[1][edit[1]] = util.clamp(step[1][edit[1]]+d, 1, commands)
else
seqEnd["A"] = util.clamp(seqEnd["A"]+d,seqStart["A"]+1,16)
end
else
if keydown[2] == 0 then
step[2][edit[2]] = util.clamp(step[2][edit[2]]+d, 1, commands)
else
seqEnd["B"] = util.clamp(seqEnd["B"]+d,seqStart["B"]+1,16)
end
end
end
redraw()
end
down_time = 0
function key(n,z)
if n == 1 then
keydown[1] = z
if z == 1 then
down_time = util.time()
else
hold_time = util.time() - down_time
if hold_time > 1 then
pageNum = (pageNum % 2) + 1
print(pageNum)
end
end
elseif n == 2 then
keydown[2] = z
if z == 1 then
down_time = util.time()
else
hold_time = util.time() - down_time
if hold_time < 1 then
activeSeq = 1 - activeSeq
else
-- nothing for now
end
end
elseif n == 3 then
keydown[3] = z
if z == 1 then
down_time = util.time()
else
hold_time = util.time() - down_time
if activeSeq == 0 then
if hold_time < 1 then
randomize_A()
else
for i=1,#step[1] do
step[1][i] = 1
end
end
else
if hold_time < 1 then
randomize_B()
else
for i=1,#step[2] do
step[2][i] = 1
end
end
end
end
end
redraw()
end
function randomize_A()
for i=seqStart["A"],seqEnd["A"] do
step[1][i] = math.random(commands)
end
end
function randomize_B()
for i=seqStart["B"],seqEnd["B"] do
step[2][i] = math.random(commands)
end
end
function fuck_up_the_midi()
for i = 1,127 do
midi_out_device:note_off(i, nil, params:get("midi_out_channel_A"))
midi_out_device:note_off(i, nil, params:get("midi_out_channel_B"))
end
end
function cleanup()
crow.clear()
crow.reset()
if params:get("output") == 2 or 3 then
crow.ii.jf.mode(0)
end
fuck_up_the_midi()
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment