Last active
August 7, 2018 14:17
-
-
Save martinmestres/9db4270028a6d5e1d0b1b42a23c2537e to your computer and use it in GitHub Desktop.
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
-- 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 | |
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
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 tableyou can use
util.clamp
from the norns library to replace lines 69-71. you could even put it on line 68 for extra-tersenessi 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 putx = 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 realizedite
is short for iterator. the idiom across most languages is that this is always just calledi
. once i rewrote it usingi
everything made a lot more sense. i'd also renameshf
to something a little more descriptive likeshuffle
orswap
.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!