Skip to content

Instantly share code, notes, and snippets.

@martinmestres
Last active August 7, 2018 14:17
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 martinmestres/9db4270028a6d5e1d0b1b42a23c2537e to your computer and use it in GitHub Desktop.
Save martinmestres/9db4270028a6d5e1d0b1b42a23c2537e to your computer and use it in GitHub Desktop.
-- Markov's Playground
-- --------------------------------
-- Page 1:
-- Key 1 change PAGE
-- Key 2 randomize SCALE
-- Key 3 randomize PROBABILITIES
--
-- Page 2:
-- Key 1 change PAGE
-- Key 2 PLAY recorded LOOP
-- Key 3 record LOOP
-- Enc 1 LOOP LENGTH
-- Enc 2 NOTE ON probability
engine.name = "PolyPerc"
local loopLength = 8 -- Loops Length {8,8} ???
local Scale = {0,2,4,5,7,9,11}
local Prob = {{},{},{},{},{},{},{}}
local Loop1 = {{".",".",".",".",".",".",".",".",".",".",".",".",".",".",".","."},
{".",".",".",".",".",".",".",".",".",".",".",".",".",".",".","."}}
local loopPos = 0
local writeLoop = 0
local playLoop = 0
local newNote = 1
local noteSel = 1
local loopSel = 1
local loopRecSel = 1
local pageNumber = 1
local noteOnProb = 90
local alternate = {{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}
local restCounter = 0
local anchor = 1
local blink = 1
local rollResult
local loopStateMemory = 0
local altControls = 0
params:add_number("bpm",40,200,100)
params:add_number("root note",30,90,60)
params:add_number("rest counter limit",1,16,2)
function init()
math.randomseed( os.time() )
engine.cutoff(4000)
params:add_control("cutoff",controlspec.new(60,10000,'exp',2,4000,'hz'))
params:set_action("cutoff", function(x) engine.cutoff(x) end)
engine.amp(1)
engine.pw(0.5)
rndProb()
counter = metro.alloc(count, 0.15, -1)
counter:start()
end
-- Counter
function count()
blinker()
counter.time = (60 / (params:get("bpm"))) / 4 --Change Tempo
if playLoop == 0 then --Check LOOPER OFF state
rollResult = noteOnRoll()
if rollResult == 1 then
noteSel = probRoll()
engine.hz(midi_to_hz(params:get("root note") + noteSel)) --Play note
restCounter = 0 --Rester Counter
else actRestCounter()
end
looper() --If
else
rollResult = noteOnRoll()
looper()
if tonumber(Loop1[loopSel][loopPos]) ~= nil then -- Check LOOPER ON state
engine.hz(midi_to_hz(params:get("root note") + Loop1[loopSel][loopPos]))
if loopSel == loopRecSel then
for i=1, 7 do -- LOOP path update
if Loop1[loopSel][loopPos] == Scale[i] then
newNote = i
break
end
end
else noteSel = probRoll() -- Keep on markoving if LOOP ~ REC
end
end
end
redraw()
end
-- LOOPER
function looper()
loopPos = util.clamp(((loopPos + 1) % (loopLength+1)), 1, loopLength)
if writeLoop == 1 then
if rollResult == 0 then
Loop1[loopRecSel][loopPos] = "."
else
Loop1[loopRecSel][loopPos] = noteSel
end
end
end
-- Random Scale Generator
function rndScale()
for i=2, 7 do
Scale[i] = Scale[i-1] + math.random(1,3) -- Creates Scale{}
end
end
-- Random Prob Gen per note --REVOIR SYSTEME D'ALEATOIRE
function rndProb() --FAIRE DE LA FONCTION LE MELANGE DUNE TABLE
for i=1, 7 do --METTRE CA EN CAS DE LONG PRESS DANS KEY3
remain = 100
deck = shuffleTable{1,2,3,4,5,6,7} -- random distribution order
for i=1, math.random(0, 10) do deck = shuffleTable(deck) end -- even more deck shuffle
for j=1, 7 do
Prob[i][deck[j]] = util.clamp((math.random(0, remain)), 0, 90) -- Mixup tables + prevent 100%
remain = (remain - Prob[i][deck[j]])
end
end
end
-- Note On roll
function noteOnRoll()
local Test = math.random(100)
if Test >= noteOnProb then return 0
else return 1
end
end
-- Midi to Hz
function midi_to_hz(note)
local hz = (440 / 32) * (2 ^ ((note - 9) / 12))
return hz
end
-- Prob roll
function probRoll()
currentNote = 1
repeat
local Test = math.random(100)
if Test >= Prob[newNote][currentNote] then
currentNote = currentNote+1
if currentNote > 7 then currentNote = 1 end
x = false
else x = true end
until x == true
newNote = currentNote
if restCounter >= params:get("rest counter limit") then --check Restcounter and reset
newNote = anchor end --Anchor HERE
return Scale[newNote]
end
-- shuffle table content
function shuffleTable(t)
for i = #t, 2, -1 do
local swap = math.random(1, i)
t[i], t[swap] = t[swap], t[i]
return t
end
end
-- Rest Counter
function actRestCounter()
restCounter = restCounter + 1
end
--Blinker
function blinker()
blink = (blink + 7) % 14
end
-- FPS
function fps()
end
-- BUTTONS
function key(n, z)
-- KEY1 activate alternative controls
if n == 1 and z == 1 then altControls = 1
elseif n == 1 and z == 0 then altControls = 0
end
-------------KEYPAGE1----------------------------------------------------
if pageNumber == 1 then
if altControls == 1 then -- ALT CONTROLS
-- PAGE 1 Key2 change Scale
if n == 2 and z == 1 then
rndScale() end
-- PAGE 1 Key 3 chang Probs
if n == 3 and z == 1 then
rndProb()
end
end
-------------KEYPAGE2----------------------------------------------------
else
if altControls == 1 then -- ALT CONTROLS
-- Loop REC Selector
if n == 2 and z == 1 then loopRecSel = 1
elseif n == 3 and z == 1 then loopRecSel = 2
end
else -- BASE CONTROLS
if n == 2 and z == 1 then -- LOOP SWITCH ON/OF
playLoop = (playLoop + 1) % 2
loopStateMemory = 1
writeLoop = 0
if playLoop == 0 then loopStateMemory = 0 end
end
if n == 3 and z == 1 then
writeLoop = 1
else
writeLoop = 0
if loopStateMemory == 1 then playLoop = 1 end
end
end
end
-------------END----------------------------------------------------
redraw()
end
--ENCODERS
function enc(n, d)
--Page change
if altControls == 0 and n == 1 then
pageNumber = util.clamp((pageNumber + d),1,2)
end
-------------ENCPAGE1----------------------------------------------------
if pageNumber == 1 then
if altControls == 1 then -- ALT CONTROLS
-- Anchor SELECTOR
if n == 3 then
anchor = util.clamp((anchor + d),1,7)
redraw()
end
-- Note selected note MANUAL change
if n == 2 then
Scale[anchor] = util.clamp(Scale[anchor] + d, 0, 24)
end
end
if altControls == 0 then -- BASIC CONTROLS
-- Note On Prob
if n == 3 then
noteOnProb = util.clamp((noteOnProb + d), 1, 100)
redraw()
end
end
end
-------------ENCPAGE2----------------------------------------------------
if pageNumber == 2 then
-- Loop Selection
if n == 3 then
loopSel = util.clamp((loopSel + d), 1, 2)
end
-- Loop Length
if n == 1 and altControls == 1 then
loopLength = util.clamp((loopLength + d), 1, 16)
redraw()
end
--Table SHIFT
if n == 2 then
--RIGHT
if d-1 == 0 then
local memory = Loop1[loopSel][loopLength]
table.remove(Loop1[loopSel], loopLength)
table.insert(Loop1[loopSel],1,memory)
--LEFT
else
local memory = Loop1[loopSel][1]
table.remove(Loop1[loopSel], 1)
table.insert(Loop1[loopSel],loopLength,memory)
end
end
end
end
function redraw()
screen.clear()
--------------------------------------------------------PAGE1----------------------------------------------------
if pageNumber == 1 then
screen.aa(0)
screen.font_size(8)
-- Draws Notes
for i=1, 7 do
screen.move((15*i)+1, 6)
if i == anchor then --Anchor blink Feedback
screen.level(blink)
else screen.level(10)
end
screen.font_face(1)
screen.text_center(Scale[i])
-- Draws probability for each note
for j=1, 7 do
screen.move((15*i)+1, 10 + (j*7))
screen.level(1)
screen.text_center(Prob[i][j])
end
end
-- Line
screen.move(11, 9)
screen.line(112, 9)
screen.stroke()
-- Curent note Feedback
screen.rect((newNote*15-1)+1,8,4,1)
screen.level(5)
screen.stroke()
-- NoteOne Probability
screen.move(120, 64)
screen.level(5)
screen.text_center(noteOnProb .. "%")
--------------------------------------------------------PAGE2----------------------------------------------------
else
-- Line
screen.level(2 - loopSel) -- Loop 1 selection FeedBack
screen.rect(0, 30, 128, 4)
screen.fill()
screen.level(-1 + loopSel) -- Loop 2 selection FeedBack
screen.rect(0, 45, 128, 4)
screen.fill()
-- Looper Memory feedback
for ii=1, 2 do
for i=1, loopLength do
screen.move(((127/loopLength)*i)-4, ((15+(ii*15)) - alternate[ii][i]))
-- Jumping numbers feedback
if loopSel == ii then
if playLoop == 1 then
if tonumber(Loop1[ii][i]) ~= nil then -- Marche mais entraine ligne du dessous
alternate[ii][i] = (alternate[ii][i] + 1) % math.random(2, 4)
end
else alternate[ii] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
end
else alternate[ii] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
end
-- Looper current position feedback
if i == loopPos then screen.level(15)
else screen.level(4)
end
screen.font_face(1)
screen.text_center(Loop1[ii][i])
end
end
-- Looper On/oFF Feedback rect
screen.move(11, 9)
if playLoop == 1 then --Loop On Blink
screen.rect(115, 58, 6, 6)
screen.level(blink)
screen.fill()
elseif loopStateMemory == 1 then --Loop On, Write On Steady State
screen.rect(115, 58, 6, 6)
screen.level(15)
screen.fill()
end
if playLoop == 0 and loopStateMemory == 0 then --Loop Off, Write Off
screen.rect(116, 59, 5, 5)
screen.level(1)
screen.stroke()
end
-- Write On/oFF Feedback
screen.move(80, 64)
if writeLoop == 1 then screen.level(blink)
else screen.level(1)
end
screen.text("REC".. loopRecSel)
end
-----------------------------------------------------ALL-----------------------------------------
--Curent page
screen.move(0, 64)
screen.level(4)
screen.text("P" .. pageNumber)
screen.update()
end
@trentgill
Copy link

there's a bunch of indentation issues which make the code a bit hard to read, especially because of how Lua leans on indentation for signaling the control flow. See init(), line50, rndProb(), line85, line87, line90. rndProb() is particularly difficult to understand because the nested-for looks as if it follows the preceeding one.

line 28 'font_face' call has no action.

rndScale at line 54 could start with i=2 that way you don't need that conditional. whenever i see an iterator not starting at 1 it makes me ask why, so i don't think it loses any clarity.

i would change how probRoll() works by making it return line96 rather than set noteSel. there's nothing about the name 'probRoll()' that tells me noteSel is going to be updated. Then line47 becomes noteSel = probRoll() which for me is better style & isolates the functionality.

again for rndProb() and shuffleTable(t) i'd use more functional style.
lines65+66 become:
deck = shuffleTable{1,2,3,4,5,6,7}
and shuffleTable needs to return t to pass through the modified table

you can use util.clamp from the norns library to replace lines 69-71. you could even put it on line 68 for extra-terseness

i didn't realize w was used by the redraw() function and was going to suggest moving it near probRoll() for clarity. instead i'd strongly suggest giving it a meaningful name. generally single character names are only used locally within functions.

i'd put the end on line93 at the end of line 92, or put x = true on it's own line. the if is short enough that i think 1 line is ok, but having a separate line might more clearly show that it is the alternative to line88 (this is amplified by the extra indent on line90, but also having another nested if).

i had a bit of a hard time with shuffleTable(t). in particular the ite = #t. i thought for ages it was doing something magical or more complex. then i realized ite is short for iterator. the idiom across most languages is that this is always just called i. once i rewrote it using i everything made a lot more sense. i'd also rename shf to something a little more descriptive like shuffle or swap.

inside of key() you could roll the nested ifs into a single if with 'and'. makes it more terse and i think clearer:
if n == 2 and z == 1 then

//

in general the logic is solid. most of my suggestions are stylistic and you can certainly ignore some if you choose (but please indent your code correctly!). i like that all of your functions are less than 20 lines (we'll ignore redraw() ).

i'm a beginner to Lua though, so some of my critiques might actually be about idioms of Lua that I don't know. so.. grain of salt! hope it helps!

@martinmestres
Copy link
Author

Thank you, your advices will certainly help me a lot. I will try to apply them one by one without breaking everything.

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