Last active
February 8, 2020 18:49
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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