Skip to content

Instantly share code, notes, and snippets.

@millxing
Last active February 8, 2020 18:49
Show Gist options
  • Save millxing/3b347fe7a69f72387fac0668871fca9f to your computer and use it in GitHub Desktop.
Save millxing/3b347fe7a69f72387fac0668871fca9f 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!
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment