Skip to content

Instantly share code, notes, and snippets.

@tildebyte
Forked from millxing/quence01.lua
Last active February 8, 2020 18:56
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 tildebyte/ebe10c470fb232180a57b8aa585e7952 to your computer and use it in GitHub Desktop.
Save tildebyte/ebe10c470fb232180a57b8aa585e7952 to your computer and use it in GitHub Desktop.
This is the latest version (as of 11/4/2018) of QUENCE, a probabilistic MIDI sequencer for norns and grid (monome.org)
-- Q * U * E * N * C * E
--
-- a probababilistic
-- 4-track MIDI sequencer
-- for norns and grid
--
-- Rob Schoen
-- millxing at gmail
-- inspired by Turing Machine, Fugue Machine, and Physical (Norns Study #4)
-- random tips:
-- bottom row is always the toolbar - pause, mutes for tracks 1-4, lock all, clear all, select tracks 1-4, and select settings page
-- hold encoder 3 to see the midi notes in the sequence for the current track
-- on the settings page, the eight buttons in rows 5-6, cols 13-16 are currently unassigned,
-- but for now you can hit any of them to resync all the sequences
-- music = require 'mark_eats/musicutil' -- super useful. Thanks Mark!
music = require 'musicutil'
beatclock = require 'beatclock'
function init()
opening_animation()
math.randomseed(os.time())
-- initalize variables
position = {}
tempomod = {}
seqlen = {}
dispersion = {}
for j = 1, 4 do
position[j] = 16
tempomod[j] = 1
seqlen[j] = 16
dispersion[j] = 5
end
press = 0
xy = 0
transpose = 0
page = 1
tpage = -99
pagecopy = 1
lock = 0
pause = 1
ct = 0
toniclist = {'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'}
tonicnum = 1
-- probabilities for randomness and rest frequencies
changelist = {
0,
(1 / 6) ^ 1.5,
(2 / 6) ^ 1.5,
(3 / 6) ^ 1.5,
(4 / 6) ^ 1.5,
(5 / 6) ^ 1.5,
1.00
}
rflist = {
0,
(1 / 6) ^ 1.5,
(2 / 6) ^ 1.5,
(3 / 6) ^ 1.5,
(4 / 6) ^ 1.5,
(5 / 6) ^ 1.5,
1
}
-- set defaults
change = {7, 1, 1, 1}
restfreq = {1, 1, 1, 1}
mnote = {0, 0, 0, 0}
mute = {0, 0, 0, 0}
-- norns and grid parameters
maxscreen = 6
maxgrid = 10
inact = 4
-- set up musical scale
mode = 12 -- default = major pentatonic
center = 48 + (tonicnum - 1) -- tonic center
scale = music.generate_scale_of_length(center - 12, music.SCALES[mode].name, 24)
tempo = 60 -- default tempo
-- initalize sequences
steps = {}
rests = {}
steps_copy = {}
rests_copy = {}
for j = 1, 4 do
steps[j] = {}
rests[j] = {}
for i = 1, seqlen[j] do
steps[j][i] = 15
rests[j][i] = 1
steps_copy[i] = steps[1][i]
rests_copy[i] = rests[1][i]
end
end
-- clock settings
clk = beatclock.new()
clk_midi = midi.connect()
clk_midi.event = clk.process_midi
clk.on_step = count
clk.on_select_internal = function()
clk:start()
end
clk.on_select_external = function()
print('external')
end
clk:add_clock_params()
params:set('bpm', tempo)
redraw()
grid_redraw()
end
-- redraw screen
function redraw()
screen.clear()
screen.level(maxscreen)
screen.move(0, 10)
screen.text('global bpm : ' .. params:get('bpm'))
screen.move(0, 20)
screen.text(
'tonic and scale : ' .. toniclist[tonicnum] .. ' ' .. music.SCALES[mode].name
)
screen.move(0, 30)
screen.text(
'sequence lengths : ' ..
seqlen[1] .. ' ' .. seqlen[2] .. ' ' .. seqlen[3] .. ' ' .. seqlen[4]
)
screen.move(0, 40)
screen.text(
'tempo modifiers : ' ..
tempomod[1] .. ' ' .. tempomod[2] .. ' ' .. tempomod[3] .. ' ' .. tempomod[4]
)
screen.move(0, 50)
screen.text(
'dispersions : ' ..
dispersion[1] ..
' ' .. dispersion[2] .. ' ' .. dispersion[3] .. ' ' .. dispersion[4]
)
screen.update()
end
-- redraw grid
function grid_redraw()
if tpage ~= page then
g:all(0)
grid_redraw_ctrl()
end
if page == 0 and tpage ~= page then
grid_redraw_home()
end
if page > 0 then
grid_redraw_page()
end
g:refresh()
tpage = page
end
-- redraw toolbar at bottom row of grid
function grid_redraw_ctrl()
g:led(1, 8, pause == 1 and maxgrid or inact) --pause
for i = 3, 6 do
g:led(i, 8, mute[i - 2] == 1 and maxgrid or inact) --mutes(1-4)
end
g:led(8, 8, lock == 1 and maxgrid or inact) --lock all
g:led(9, 8, inact) --randomize all
for i = 11, 14 do
g:led(i, 8, (page == (i - 10)) and maxgrid or inact) --select track pages (1-4)
end
g:led(16, 8, page == 0 and maxgrid or inact) --select home page
end
-- redraw home page of grid
function grid_redraw_home()
g:led(1, 2, inact)
g:led(1, 3, inact) -- seqlen 1
g:led(2, 2, inact)
g:led(2, 3, inact) -- seqlen 2
g:led(3, 2, inact)
g:led(3, 3, inact) -- seqlen 3
g:led(4, 2, inact)
g:led(4, 3, inact) -- seqlen 4
g:led(6, 2, inact)
g:led(6, 3, inact) -- bpm coarse
g:led(7, 2, inact)
g:led(7, 3, inact) -- bpm fi
g:led(10, 2, inact)
g:led(10, 3, inact) -- tonic
g:led(11, 2, inact)
g:led(11, 3, inact) -- scale
g:led(13, 2, inact)
g:led(13, 3, inact) -- tempo mod 1
g:led(14, 2, inact)
g:led(14, 3, inact) -- tempo mod 2
g:led(15, 2, inact)
g:led(15, 3, inact) -- tempo mod 3
g:led(16, 2, inact)
g:led(16, 3, inact) -- tempo mod 4
g:led(1, 5, inact)
g:led(1, 6, inact) -- dispersion 1
g:led(2, 5, inact)
g:led(2, 6, inact) -- dispersion 2
g:led(3, 5, inact)
g:led(3, 6, inact) -- dispersiom 3
g:led(4, 5, inact)
g:led(4, 6, inact) -- dispersion 4
g:led(13, 5, inact)
g:led(13, 6, inact) -- unassigned
g:led(14, 5, inact)
g:led(14, 6, inact) -- unassigned
g:led(15, 5, inact)
g:led(15, 6, inact) -- unassigned
g:led(16, 5, inact)
g:led(16, 6, inact) -- unassigned
end
-- redraw the track view for the selected track
function grid_redraw_page()
-- turn off all leds on the top 2 rows
for i = 1, seqlen[page] do
g:led(i, 1, 0)
g:led(i, 2, 0)
end
-- draw top 2 rows (sequence position and scale degrees)
for i = 1, seqlen[page] do
g:led(i, 1, i == position[page] and maxgrid or inact)
if rests[page][i] == 0 and steps[page][i] > 0 then
val = math.floor((steps[page][i] / #scale) * maxgrid) + 1
if val > maxgrid then
val = maxgrid
end
if val < 0 then
val = 1
end
g:led(i, 2, val)
else
g:led(i, 2, 0)
end
end
-- draw randomness selector and rest frequency selector on row 4
for j = 1, 7 do
g:led(j, 4, j == change[page] and maxgrid or inact)
g:led(j + 9, 4, j == restfreq[page] and maxgrid or inact)
end
-- draw various buttons on rows 6 and 7
g:led(1, 6, (press == 106) and maxgrid or inact) --copy
g:led(2, 6, (press == 206) and maxgrid or inact) --paste
g:led(4, 6, (press == 406) and maxgrid or inact) --shift ->
g:led(5, 6, (press == 506) and maxgrid or inact) --shift <-
g:led(12, 6, (press == 1206) and maxgrid or inact) -- transpose up
g:led(13, 6, (press == 1306) and maxgrid or inact) -- transpose down
g:led(15, 6, (press == 1506) and maxgrid or inact) -- invert
g:led(16, 6, (press == 1606) and maxgrid or inact) -- reverse
g:led(8, 7, (press == 807) and maxgrid or inact) --track lock
g:led(9, 7, (press == 907) and maxgrid or inact) --track clear
end
-- connect grid
g = grid.connect()
-- midi code
m = midi.connect()
m.event = function(data)
local d = midi.to_msg(data)
if d.type == 'note_on' then
transpose = d.note - 60
end
end
-- grid events
g.event = function(x, y, z)
xy = x * 100 + y -- integer code for coordinates of grid event
-- settings page button press events ---------------------------------------------------
if z == 1 and page == 0 then
press = 0
-- coarse global tempo up
if xy == 602 then
tempo = tempo + 10
params:set('bpm', tempo)
press = xy
end
-- coarse global tempo down
if xy == 603 then
tempo = tempo - 10
if tempo < 1 then
tempo = 10
end
params:set('bpm', tempo)
press = xy
end
-- fine global tempo up
if xy == 702 then
tempo = tempo + 1
params:set('bpm', tempo)
press = xy
end
-- fine global tempo down
if xy == 703 then
tempo = tempo - 1
if tempo < 1 then
tempo = 1
end
params:set('bpm', tempo)
press = xy
end
-- tonic up
if xy == 1002 then
tonicnum = tonicnum + 1
if tonicnum > 12 then
tonicnum = 1
end
center = 48 + (tonicnum - 1) -- tonic center
scale = music.generate_scale_of_length(center - 12, music.SCALES[mode].name, 24)
press = xy
end
-- tonic down
if xy == 1003 then
tonicnum = tonicnum - 1
if tonicnum < 1 then
tonicnum = 12
end
center = 48 + (tonicnum - 1) -- tonic center
scale = music.generate_scale_of_length(center - 12, music.SCALES[mode].name, 24)
press = xy
end
-- scale up
if xy == 1102 then
mode = mode + 1
if mode > 47 then
mode = 1
end
center = 48 + (tonicnum - 1) -- tonic center
scale = music.generate_scale_of_length(center - 12, music.SCALES[mode].name, 24)
press = xy
end
-- scale down
if xy == 1103 then
mode = mode - 1
if mode < 1 then
mode = 47
end
center = 48 + (tonicnum - 1) -- tonic center
scale = music.generate_scale_of_length(center - 12, music.SCALES[mode].name, 24)
press = xy
end
-- sequence lengths for tracks 1-4
if x > 0 and x < 5 and (y == 2 or y == 3) then
i = x
if y == 2 then
seqlen[i] = seqlen[i] + 1
if seqlen[i] > 16 then
seqlen[i] = 16
end
else
seqlen[i] = seqlen[i] - 1
if seqlen[i] < 1 then
seqlen[i] = 16
end
end
position[i] = 16
press = xy
end
-- dispersion parameter for tracks 1-4
if x > 0 and x < 5 and (y == 5 or y == 6) then
i = x
if y == 5 then
dispersion[i] = dispersion[i] + 1
if dispersion[i] > 10 then
dispersion[i] = 10
end
else
dispersion[i] = dispersion[i] - 1
if dispersion[i] < 0 then
dispersion[i] = 0
end
end
press = xy
end
-- tempo modifier for tracks 1-4
if x > 12 and (y == 2 or y == 3) then
i = x - 12
if y == 2 then
tempomod[i] = tempomod[i] + 1
if tempomod[i] > 8 then
tempomod[i] = 8
end
else
tempomod[i] = tempomod[i] - 1
if tempomod[i] < 1 then
tempomod[i] = 1
end
end
press = xy
end
-- re-sync all sequences
if x > 12 and (y == 5 or y == 6) then
syncTracks()
press = xy
end
end
-- track page button press events ---------------------------------------------------
if z == 1 and page > 0 then
-- change probability (row 4, left side)
if y == 4 and x < 8 then
change[page] = x
end
-- rest frequency (row 4, right side)
if y == 4 and x > 9 then
restfreq[page] = x - 9
end
-- sequence position (top row)
if y == 1 then
position[page] = x
end
-- toggle rest (2nd row from top)
if y == 2 then
rests[page][x] = 1 - rests[page][x]
end
-- copy sequence (row 6, col 1)
if y == 6 and x == 1 then
for i = 1, 16 do
steps_copy[i] = steps[page][i]
rests_copy[i] = rests[page][i]
end
pagecopy = page
press = xy
end
-- paste sequence (row 6, col 2)
if y == 6 and x == 2 then
for i = 1, 16 do
steps[page][i] = steps_copy[i]
rests[page][i] = rests_copy[i]
end
seqlen[page] = seqlen[pagecopy]
change[page] = 1
press = xy
end
-- shift left (row 6, col 4)
if xy == 406 then
shift_left()
press = xy
end
-- shift right (row 6, col 5)
if xy == 506 then
shift_right()
press = xy
end
-- scalar transpose down (row 6, col 12)
if xy == 1206 then
for i = 1, seqlen[page] do
steps[page][i] = steps[page][i] - 1
if steps[page][i] < 1 then
steps[page][i] = #scale
end
end
press = xy
end
-- scalar transpose up (row 6 col 13)
if xy == 1306 then
for i = 1, seqlen[page] do
steps[page][i] = steps[page][i] + 1
if steps[page][i] > #scale then
steps[page][i] = 1
end
end
press = xy
end
-- invert (row 6 col 15)
if xy == 1506 then
for i = 1, seqlen[page] do
steps[page][i] = (12 - steps[page][i]) + 12
end
press = xy
end
-- reverse (row 6 col 16)
if xy == 1606 then
temp_steps = deepcopy(steps[page])
temp_rests = deepcopy(rests[page])
for i = 1, seqlen[page] do
steps[page][i] = temp_steps[seqlen[page] + 1 - i]
rests[page][i] = temp_rests[seqlen[page] + 1 - i]
end
press = xy
end
-- lock track (row 7 col 8 -- sets randomness for track to 0)
if xy == 807 then
change[page] = 1
press = xy
end
-- clear track (row 7 col 9)
if xy == 907 then
change[page] = 1
restfreq[page] = 1
for j = 1, 16 do
steps[page][j] = 15
rests[page][j] = 1
end
press = xy
end
end
-- toolbar button press events ---------------------------------------------------
if z == 1 and y == 8 then
-- pause all sequences (row 8, col 1)
if xy == 108 then
pause = 1 - pause
if pause == 0 then
clk:start()
else
clk:stop()
end
-- clear all note ons
if pause == 1 then
for p = 1, 4 do
if mnote[p] > 0 then
m.note_off(mnote[p], 0, p)
end
end
end
end
-- mute tracks (row 8 cols 3-6)
if y == 8 and (x >= 3 and x <= 6) then
mute[x - 2] = 1 - mute[x - 2]
end
-- lock all tracks (row 8 col 8)
if y == 8 and x == 8 then
lock = 1 - lock
for i = 1, 4 do
change[i] = 1
end
g:led(x, y, maxgrid)
g:refresh()
end
-- clear all tracks (row 8 col 9)
if y == 8 and x == 9 then
for i = 1, 4 do
change[i] = 1
restfreq[i] = 1
for j = 1, seqlen[i] do
steps[i][j] = 15
rests[i][j] = 1
end
end
press = xy
end
-- select track page (row 8 cols 11-14)
if y == 8 and (x >= 11 and x <= 14) then
page = x - 10
end
-- select settings page (row 8 col 16)
if y == 8 and x == 16 then
page = 0
end
-- light up a pressed button
if press > 0 then
g:led(x, y, maxgrid)
g:refresh()
end
end
-- button unpressed events
if z == 0 then
if press > 0 then
g:led(x, y, inact)
press = 0
else
g:led(x, y, 0)
g:refresh()
end
end
-- pause led
if pause == 1 then
g:led(1, 8, maxgrid)
else
g:led(1, 8, inact)
end
-- mute leds
for i = 1, 4 do
if mute[i] == 1 then
g:led(2 + i, 8, maxgrid)
else
g:led(2 + i, 8, inact)
end
end
-- track select leds
for i = 1, 4 do
if page == i then
g:led(10 + i, 8, maxgrid)
else
g:led(10 + i, 8, inact)
end
end
-- settings page led
if page == 0 then
g:led(16, 8, maxgrid)
else
g:led(16, 8, inact)
end
-- turn off lock led if any track is unlocked
for i = 1, 4 do
if change[i] > 1 then
lock = 0
end
end
if lock == 1 then
g:led(8, 8, maxgrid)
else
g:led(8, 8, inact)
end
-- redraw
redraw()
grid_redraw()
end
function count()
ct = ct + 1
-- moves the sequence ahead by one step and turns on/off notes
for p = 1, 4 do
-- advance the sequence position, depending on the tempo modifier
if ct % tempomod[p] == 0 then
position[p] = (position[p] % seqlen[p]) + 1
-- update the sequence
update_sequence(p)
-- turn off the last note
if mnote[p] > 0 or mute[p] == 1 then
m.note_off(mnote[p], 0, p)
end
note = scale[steps[p][position[p]]]
-- turn on a note unless there is a rest
if rests[p][position[p]] == 0 and note ~= nil then
note = note + transpose
if note > 0 and mute[p] == 0 then
m.note_on(note, 90, p)
mnote[p] = note
end
end
end
end
grid_redraw()
end
function update_sequence(p)
-- updates each sequence in a probabilistic manner
-- the dispersion parameter controls how big of a jump the sequence can make
chg = changelist[change[p]]
rfq = rflist[restfreq[p]]
if (math.random() < chg) then
tposition = position[p] - 1
if tposition < 1 then
tposition = seqlen[p]
end
delta = round(box_muller() * (dispersion[p] / 5))
delta = delta + round((15 - steps[p][position[p]]) * .05)
steps[p][position[p]] = steps[p][tposition] + delta
if steps[p][position[p]] > #scale then
steps[p][position[p]] = #scale - (steps[p][position[p]] - #scale)
end
if steps[p][position[p]] < 1 then
steps[p][position[p]] = 1 - (steps[p][position[p]])
end
if (math.random() < rfq) then
rests[p][position[p]] = 1
else
rests[p][position[p]] = 0
end
end
end
function key(n, z)
-- while held, button 3 displays the midi notes of the sequence on the selected track
if n == 3 and z == 1 then
screen.clear()
bb = ' '
for i = 1, seqlen[page] do
aa = scale[steps[page][i]]
if rests[page][i] == 1 then
aa = 0
end
if aa < 100 then
if aa == 0 then
aa = (' ' .. aa)
else
aa = (' ' .. aa)
end
end
bb = (bb .. aa .. ' ')
end
screen.move(0, 10)
screen.text('Track #' .. page)
for k = 1, 4 do
screen.move(0, 20 + 10 * k)
cc = string.sub(bb, (k - 1) * 16 + 1, (k - 1) * 16 + 16)
screen.text(cc)
end
screen.update()
end
if n == 3 and z == 0 then
redraw()
end
end
function shift_left()
-- shifts the sequence to the left, wrapping the first note to the end of the sequence
-- rewrite this using deepcopy
local stp = steps[page][1]
local rst = rests[page][1]
for i = 1, (seqlen[page] - 1) do
steps[page][i] = steps[page][i + 1]
rests[page][i] = rests[page][i + 1]
end
steps[page][seqlen[page]] = stp
rests[page][seqlen[page]] = rst
end
function shift_right()
-- shifts the sequence to the right, wrapping the last note to the start of the sequence
-- rewrite this using deepcopy
local stp = steps[page][seqlen[page]]
local rst = rests[page][seqlen[page]]
for i = seqlen[page], 2, -1 do
steps[page][i] = steps[page][i - 1]
rests[page][i] = rests[page][i - 1]
end
steps[page][1] = stp
rests[page][1] = rst
end
function max(a)
--return the max of a numeric table
local values = {}
for k, v in pairs(a) do
values[#values + 1] = v
end
table.sort(values)
return values[#values]
end
function min(a)
--return the max of a numeric table
local values = {}
for k, v in pairs(a) do
values[#values + 1] = v
end
table.sort(values)
return values[1]
end
function calcScaleDegrees()
-- returns the number of scale degrees in given scale
-- needs improvement
if scale[8] - scale[1] == 12 then
scaleDegrees = 7
else
if scale[6] - scale[1] == 12 then
scaleDegrees = 5
else
if scale[7] - scale[1] == 12 then
scaleDegrees = 6
else
if scale[9] - scale[1] == 12 then
scaleDegrees = 8
end
end
end
end
if scaleDegrees == 0 then
print('scale degrees unknown')
scaleDegrees = 7
end
return scaleDegrees
end
function deepcopy(orig)
-- make a copy of a table instead of making a direct reference
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[deepcopy(orig_key)] = deepcopy(orig_value)
end
setmetatable(copy, deepcopy(getmetatable(orig)))
else -- number, string, boolean, etc
copy = orig
end
return copy
end
function reverse(orig)
-- reverse a numeric table
local i,
j = 1, #orig
while i < j do
orig[i],
orig[j] = orig[j], orig[i]
i = i + 1
j = j - 1
end
end
function syncTracks()
for i = 1, 4 do
position[i] = 0
end
end
function clearAllNotes()
for i = 1, 4 do
for j = 1, #scale do
m.note_off(scale[j], 0, i)
end
end
end
-- box_muller simulates normally distributed random numbers from uniform random numbers
function box_muller()
return math.sqrt(-2 * math.log(math.random())) * math.cos(2 * math.pi * math.random())
end
-- round to nearest integer
function round(num)
under = math.floor(num)
upper = math.floor(num) + 1
underV = -(under - num)
upperV = upper - num
if (upperV > underV) then
return under
else
return upper
end
end
-- gratuitous opening animation
function opening_animation()
for a = 8, 1, -1 do
g:all(0)
for i = 1, 8 do
for j = 1 + (a - 1), 16 - (a - 1) do
g:led(j, i, math.random(0, 15))
end
end
g:refresh()
sleep(.1)
end
for a = 1, 8 do
g:all(0)
for i = 1, 8 do
for j = 1 + (a - 1), 16 - (a - 1) do
g:led(j, i, math.random(0, 15))
end
end
g:refresh()
sleep(.1)
end
g:all(0)
g:refresh()
end
-- pause lua code
function sleep(s)
local ntime = os.clock() + s
repeat
until os.clock() > ntime
end
@tildebyte
Copy link
Author

tildebyte commented Feb 8, 2020

@tildebyte
Copy link
Author

d269ce5801cf64a29e06e8cdfb46732f953ff462_2_668x500

@tildebyte
Copy link
Author

dc05347ef67c06f9fdad89f4017f7d004240f292_2_671x500

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