Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
music = require 'mark_eats/musicutil' -- super useful. Thanks Mark!
beatclock = require 'beatclock'
-- midi code
m1 = midi.connect(1)
m2 = midi.connect(2)
--[[
m1.event = function(data)
local d = midi.to_msg(data)
if d.type == "note_on" and d.ch == 16 then
knote = d.note;
mbcount = mbcount + 1; if mbcount>32 then mbcount = 1; end
mbuffer[mbcount] = knote
end
end
]]--
m2.event = function(data)
local d = midi.to_msg(data)
channel = 99; cc = 99; value = 99
if d.type=="cc" then
--print(" type = " .. d.type .. " ch = " .. d.ch .. " cc = " .. d.cc .. " val = " .. d.val)
channel = d.ch; cc = d.cc; value = d.val
cc = cc % 16
if channel==1 and cc==0 then twist_tempo() end
if channel==1 and cc==1 then twist_tonic() end
if channel==1 and cc==2 then twist_octave(bank) end
if channel==1 and cc==4 then twist_seqlen(bank) end
if channel==1 and cc==5 then twist_random(bank) end
if channel==1 and cc==6 then twist_restfreq(bank) end
if channel==1 and cc==8 then twist_tempomod(bank) end
if channel==1 and cc==9 then twist_dispersion(bank) end
if channel==1 and cc==10 then twist_persist(bank) end
if channel==1 and cc==12 then twist_shift(bank) end
if channel==1 and cc==13 then twist_tpose(bank) end
if channel==1 and cc==14 then twist_velocity(bank) end
if channel==1 and cc==3 then twist_mix(1) end
if channel==1 and cc==7 then twist_mix(2) end
if channel==1 and cc==11 then twist_mix(3) end
if channel==1 and cc==15 then twist_mix(4) end
if channel==2 and cc==0 then twist_pause() end
if channel==2 and cc==3 then twist_mute(1) end
if channel==2 and cc==7 then twist_mute(2) end
if channel==2 and cc==11 then twist_mute(3) end
if channel==2 and cc==15 then twist_mute(4) end
if channel==2 and cc==5 then twist_lock(bank) end
if channel==4 and cc==0 then bank = 1; end
if channel==4 and cc==1 then bank = 2; end
if channel==4 and cc==2 then bank = 3; end
if channel==4 and cc==3 then bank = 4; end
if channel==5 and cc==1 then twist_scale() end
end
redraw_grid()
redraw_twister()
redraw()
end
-- connect grid
g = grid.connect()
-- redraw grid
function redraw_grid()
g.all(0)
g.led(1,1,bank==1 and 15 or 5)
g.led(1,3,bank==2 and 15 or 5)
g.led(1,5,bank==3 and 15 or 5)
g.led(1,7,bank==4 and 15 or 5)
for r = 1,8 do
for c = 0,2 do
g.led(9+c,r,((pressX==(9+c) or pressX==(13+c)) and pressY==r) and 15 or sBank[r][c+1])
g.led(13+c,r,((pressX==(9+c) or pressX==(13+c)) and pressY==r) and 15 or sBank[r][c+1])
end
end
g.refresh()
end
g.event = function(x,y,z)
if x == 1 and y == 1 then bank = 1; m2.cc(0,127,4); end
if x == 1 and y == 3 then bank = 2; m2.cc(1,127,4); end
if x == 1 and y == 5 then bank = 3; m2.cc(2,127,4); end
if x == 1 and y == 7 then bank = 4; m2.cc(3,127,4); end
if x>=9 and x<=11 then
--store
if z==1 then
pressX = x; pressY = y;
sBank[y][x-8] = 8
sequenceBank[y][x-8] = deepcopy(steps[bank])
sequenceBank2[y][x-8] = deepcopy(rests[bank])
sLength[y][x-8] = deepcopy(seqlen[bank])
rF[y][x-8] = deepcopy(restfreq[bank])
end
if z==0 then pressX = 0; pressY = 0; end
end
if x>=13 and x<=15 then
--recall
if z==1 then
pressX = x; pressY = y;
steps[bank] = deepcopy(sequenceBank[y][x-12])
rests[bank] = deepcopy(sequenceBank2[y][x-12])
seqlen[bank] = deepcopy(sLength[y][x-12])
restfreq[bank] = deepcopy(rF[y][x-12])
change[bank] = 0
waitforsync[bank] = 1
--print(y,x,steps[bank][1],steps[bank][2])
end
if z==0 then pressX = 0; pressY = 0; end
end
redraw_grid()
redraw_twister()
redraw()
end
function init()
math.randomseed(os.time())
-- initalize variables
pause = 1
ct = 0
bank = 1
midiX = 0
-- set defaults
position = {0,0,0,0}
change = {100,0,0,0}
restfreq = {25,25,25,25}
mnote = {0,0,0,0}
mute = {0,1,1,1}
mixer = {90,90,90,90}
transpose = {0,0,0,0}
velmod = {75,75,75,75}
shift = {0,0,0,0}
octave = {0,0,0,0}
tempomod = {1,1,1,1}
seqlen = {16,16,16,16}
dispersion = {15,15,15,15}
persist = {50,50,50,50}
waitforsync = {0,0,0,0}
tempo = 60
-- norns parameters
maxscreen = 12
-- grid presses
pressX = 0
pressY = 0
sequenceBank = {}
sequenceBank2 = {}
sBank = {}
sLength = {}
rF = {}
for j = 1,8 do
sBank[j] = {}
sLength[j] = {}
rF[j] = {}
sequenceBank[j] = {}
sequenceBank2[j] = {}
for i = 1,3 do
sBank[j][i] = 3
sequenceBank[j][i] = {}
sequenceBank2[j][i] = {}
sLength[j][i] = seqlen[bank]
rF[j][i] = restfreq[bank]
end
end
-- set up musical scale
toniclist = {'C','C#','D','D#','E','F','F#','G','G#','A','A#','B'}
tonicnum = 6 -- default = C
mode = 12 -- default = ionian scake
center = 48+(tonicnum-1) -- tonic center
maxscale = 30
scale = music.generate_scale_of_length(center-12,music.SCALES[mode].name,maxscale)
scale2 = music.generate_scale_of_length(48,music.SCALES[1].name,40)
mbuffer = {};
mbcount = 0;
-- initalize sequences
steps = {}; rests = {}; velos = {}
steps_copy = {}; rests_copy = {}; velos_copy = {}
for j=1,4 do
steps[j] = {}; rests[j] = {}; velos[j] = {}
for i=1,32 do
steps[j][i] = 15
rests[j][i] = 0
velos[j][i] = 0
steps_copy[i] = steps[1][i]
rests_copy[i] = rests[1][i]
velos_copy[i] = velos[1][i]
end
end
seqlen_copy = 1
bank_copy = 1
-- 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)
m2.cc(0,127,4) -- set twister to bank 1
redraw_grid()
redraw_twister()
redraw()
end
function value_to_encoder(val,mn,mx)
pct = (val-mn) / (mx-mn)
return round(127*pct)
end
function encoder_to_value(enc,mn,mx)
pct = enc / 127
return round(mn + (mx-mn) * pct)
end
--redraw twister
function redraw_twister()
for j = 0,3 do
m2.cc(0+(j*16),value_to_encoder(tempo,1,120),1)
m2.cc(1+(j*16),value_to_encoder(tonicnum,1,12),1)
m2.cc(1+(j*16),value_to_encoder(mode,1,47),5)
m2.cc(2+(j*16),value_to_encoder(octave[j+1],-3,3),1)
m2.cc(3+(j*16),value_to_encoder(mixer[1],0,127),1)
m2.cc(4+(j*16),value_to_encoder(seqlen[j+1],1,32),1)
m2.cc(5+(j*16),value_to_encoder(change[j+1],0,100),1)
m2.cc(6+(j*16),value_to_encoder(restfreq[j+1],0,100),1)
m2.cc(7+(j*16),value_to_encoder(mixer[2],0,127),1)
m2.cc(8+(j*16),value_to_encoder(tempomod[j+1],1,8),1)
m2.cc(9+(j*16),value_to_encoder(dispersion[j+1],0,100),1)
m2.cc(10+(j*16),value_to_encoder(persist[j+1],0,100),1)
m2.cc(11+(j*16),value_to_encoder(mixer[3],0,127),1)
m2.cc(12+(j*16),value_to_encoder(shift[j+1],-8,8),1)
m2.cc(13+(j*16),value_to_encoder(transpose[j+1],-7,7),1)
m2.cc(14+(j*16),value_to_encoder(velmod[j+1],0,100),1)
m2.cc(15+(j*16),value_to_encoder(mixer[4],0,127),1)
if pause==1 then m2.cc(0+(j*16),127,2) else m2.cc(0+(j*16),0,2) end
if change[j+1]==100 then m2.cc(5+(j*16),127,2) else m2.cc(5+(j*16),0,2) end
if mute[1]==1 then m2.cc(3+(j*16),127,2) else m2.cc(3+(j*16),0,2) end
if mute[2]==1 then m2.cc(7+(j*16),127,2) else m2.cc(7+(j*16),0,2) end
if mute[3]==1 then m2.cc(11+(j*16),127,2) else m2.cc(11+(j*16),0,2) end
if mute[4]==1 then m2.cc(15+(j*16),127,2) else m2.cc(15+(j*16),0,2) end
end
end
-- redraw screen
function redraw()
screen.clear()
screen.font_size(8);
screen.level(maxscreen)
local g = 0
g = g + 1;
screen.move(0,10+9*(g-1)); screen.text("scale : " .. toniclist[tonicnum] .. " " .. music.SCALES[mode].name)
g = g + 1;
screen.move(0,10+9*(g-1)); screen.text("bpm : "..params:get("bpm"))
screen.move(64,10+9*(g-1)); screen.text("bank : " .. bank)
g = g + 1
screen.move(0,10+9*(g-1)); screen.text("length : " .. seqlen[bank])
screen.move(64,10+9*(g-1)); screen.text("bpm mod : " .. tempomod[bank])
g = g + 1
screen.move(0,10+9*(g-1)); screen.text("random : " .. change[bank])
screen.move(64,10+9*(g-1)); screen.text("rest prob : " .. restfreq[bank])
g = g + 1
screen.move(0,10+9*(g-1)); screen.text("dispers : " .. dispersion[bank])
screen.move(64,10+9*(g-1)); screen.text("octave : " .. octave[bank])
g = g + 1
screen.move(0,10+9*(g-1)); screen.text("shift : " .. shift[bank])
screen.move(64,10+9*(g-1)); screen.text("tpose : " .. transpose[bank])
screen.update()
end
function count()
ct = ct + 1
-- moves the sequence ahead by one step and turns on/off notes
for p = 1,4 do
if waitforsync[p]==1 then
-- If sequence length is different from length of 1st sequence, just set position[p] to position[1]
-- otherwise wait for the sequences to sync before unmuting the sequence
if (ct % 32 == 1) or (seqlen[p]==seqlen[1]) then
waitforsync[p] = 0
position[p] = position[1]-1
end
end
-- advance the sequence position, depending on the tempo modifier
if ct % tempomod[p] == 0 and waitforsync[p]==0 then
local tpos = position[p]
position[p] = (tpos % seqlen[p]) + 1
local pos = ((tpos + shift[p]) % seqlen[p]) + 1
-- update the sequence
update_sequence(p,pos)
-- turn off the last note
if mnote[p] > 0 or mute[p]==1 then
m1.note_off(mnote[p],0,p)
end
temp = (steps[p][pos]+transpose[p]);
if temp>maxscale then; temp = maxscale; end
if temp<1 then; temp = 1; end
note = scale[temp] + (octave[p]*12)
-- turn on a note unless there is a rest
if rests[p][pos] == 0 and note~=nil and pos~=nil then
if note>0 and mute[p]==0 then
local volum = mixer[p]+velos[p][pos]
--local volum = mixer[p]
if volum>127 then; volum = 127; end
if volum<0 then; volum = 0; end
m1.note_on(note,volum,p)
mnote[p] = note
print(ct, p, position[p], pos, music.note_num_to_name(note, 1), volum)
end
end
end
end
end
function update_sequence(p,pos)
-- updates each sequence in a probabilistic manner
-- the dispersion parameter controls how big of a jump the sequence can make
chg = change[p]*.01
rfq = restfreq[p]*.01
if (math.random()<chg) then
if midiX == 0 then
tpos = pos-1
if tpos<1 then tpos = seqlen[p] end
delta = round(box_muller()*(dispersion[p]/50)*6)
steps[p][pos] = 15 + delta
if steps[p][pos]>#scale then steps[p][pos] = #scale - (steps[p][pos]-#scale) end
if steps[p][pos]<1 then steps[p][pos] = 1 - (steps[p][pos]) end
velos[p][pos] = round(box_muller()*(velmod[p]/50)*7)
if (math.random()<rfq) then
rests[p][pos] = 1
else
rests[p][pos] = 0
end
else
end
end
end
function clearAllNotes()
for i = 1,4 do
for j = 1,#scale do
m1.note_off(scale[j],0,i)
end
end
end
-------------- Twister functions --------------
function twist_tempo()
tempo = round(encoder_to_value(value,0,120))
params:set("bpm",tempo)
end
function twist_pause()
pause = 1 - pause
if pause == 0 then
clk:start()
else
clk:stop()
clearAllNotes()
end
end
function twist_tonic()
tonicnum = encoder_to_value(value,1,12)
center = 48+(tonicnum-1) -- tonic center
scale = music.generate_scale_of_length(center-12,music.SCALES[mode].name,maxscale)
end
function twist_scale()
mode = encoder_to_value(value,1,47)
center = 48+(tonicnum-1) -- tonic center
scale = music.generate_scale_of_length(center-12,music.SCALES[mode].name,maxscale)
end
function twist_octave(t)
octave[t] = encoder_to_value(value,-3,3)
end
function twist_mute(t);
mute[t] = 1 - mute[t]
end
function twist_mix(t); mixer[t] = encoder_to_value(value,0,127); end
function twist_seqlen(t); seqlen[t] = encoder_to_value(value,1,32); end
function twist_restfreq(t); restfreq[t] = encoder_to_value(value,0,100) end
function twist_random(t); change[t] = encoder_to_value(value,1,100); end
function twist_tempomod(t); tempomod[t] = encoder_to_value(value,1,8); end
function twist_dispersion(t); dispersion[t] = encoder_to_value(value,0,100); end
function twist_persist(t); persist[t] = encoder_to_value(value,0,100); end
function twist_shift(t); shift[t] = encoder_to_value(value,-8,8); print("shift " .. shift[t]); end
function twist_tpose(t); transpose[t] = encoder_to_value(value,-7,7); print("tpose " .. transpose[t]); end
function twist_velocity(t); velmod[t] = encoder_to_value(value,0,100); end
function twist_copy(t)
steps_copy = deepcopy(steps[t])
rests_copy = deepcopy(rests[t])
velos_copy = deepcopy(velos[t])
seqlen_copy = deepcopy(seqlen[t])
bank_copy = t
print("copy")
end
function twist_paste(t)
steps[t] = deepcopy(steps_copy)
rests[t] = deepcopy(rests_copy)
velos[t] = deepcopy(velos_copy)
change[t] = 0
restfreq[t] = deepcopy(restfreq[bank_copy])
seqlen[t] = deepcopy(seqlen_copy)
position[t] = deepcopy(position[bank_copy])
end
function twist_lock(t)
temp = change[t]
if temp>0 then change[t] = 0; end
if temp==100 then change[t] = 0; end
if temp==0 then change[t] = 100; end
end
-------------- Utility functions --------------
-- 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
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment