-
-
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)
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! | |
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 |
Author
tildebyte
commented
Feb 8, 2020
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment