Skip to content

Instantly share code, notes, and snippets.

@ypxk
Last active October 5, 2018 03:15
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 ypxk/4292b70f7ce7346b732b89e28431bfff to your computer and use it in GitHub Desktop.
Save ypxk/4292b70f7ce7346b732b89e28431bfff to your computer and use it in GitHub Desktop.
-- EXPENSIVE ARPEGGIATOR
-- based on norns study 4
-- grid controls arpeggio
-- midi selects notes
-- button 2 = start/stop
-- button 3 = random arp
-- encoder 1 = bpm
-- encoder 2 = skim arp/note length
-- encoder 3 = probability of note on (thanks @burn)
-- TODO:
-- find missing nil value on start
-- change vars to local
-- better solution for octaves
-- visual feedback for encoder 3
-- notes of different length
-- support for other engines
-- velocity? don't have anything that sends vel
-- mlr style range loop
-- record chord changes and iterate through
engine.name = 'Passersby'
music = require 'mark_eats/musicutil'
beatclock = require 'beatclock'
passersby = require "mark_eats/passersby"
steps = {}
position = 1
going = true
skimming = false
prob_data = {}
reveal_level = 90
skim = 0
chordsList = {31,43,51,55,58,62,63,67}
scale = chordsList
timeLast = 0
capturedNotes = music.note_nums_to_names(scale)
clk = beatclock.new()
clk_midi = midi.connect()
clk_midi.event = clk.process_midi
function init()
for i=1,16 do
table.insert(steps,math.random(8))
end
grid_redraw()
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:add_separator()
passersby.add_params()
create_sequence_data()
clk:start()
end
function create_sequence_data() --encoder 3 controlls likelyhood of note trigger
for i=1, 16 do
prob_data[i] = math.random (1, 100);
end
end
function enc(n,d)
if n == 1 then --bpm
params:delta("bpm",d)
elseif n == 2 then --skimming through arp
skimming = true
params:delta("LPG Decay", d) --clockwise = legado notes, counterclockwise = shorter notes
skim = (skim + d) % 16
if skim ~= 0 then
engine.noteOn(1,music.note_num_to_freq(scale[steps[(skim % 16)]]+12),1)
grid_redraw()
end
skimTime = util.time() -- for grid redraw
elseif n == 3 then -- controlls prob of note hit
reveal_level = reveal_level + d
if reveal_level > 100 then reveal_level = 100 end
if reveal_level < 0 then reveal_level = 0 end
end
redraw()
end
function key(n,z)
if n == 2 then -- start/stop arp
if z == 1 then
if going == false then
clk:start()
going = true
elseif going == true then
clk:stop()
going = false
end
end
elseif n == 3 then -- new sarp
if z == 1 then
steps = {}
for i=1,16 do
table.insert(steps,math.random(8)) end
create_sequence_data()
grid_redraw()
end
end
redraw()
end
function redraw()
screen.clear()
screen.level(15)
screen.move(0,30)
screen.text("bpm: "..params:get("bpm"))
screen.move(0,40)
screen.text(table.concat(capturedNotes, ' '))
screen.update()
end
g = grid.connect()
g.event = function(x,y,z) -- manual set pitch, toggle notes
if z == 1 then
if steps[x] == y then
steps[x] = 0
else
steps[x] = y
end
grid_redraw()
end
end
function grid_redraw()
g.all(0)
if skimming == false then --skimming mode overrides display
for i=1,16 do
g.led(i,steps[i],i==position and 15 or 4)
end
else
for i=1,16 do
g.led(i,steps[i],i==skim and 15 or 4)
end
if util.time() - skimTime < .5 then
skimming = false
end
end
g.refresh()
end
function count()
position = (position % 16) + 1 --determines if note plays
if prob_data[position] <= reveal_level then
engine.noteOn(1,music.note_num_to_freq(scale[steps[position]]),1)
end
grid_redraw()
end
function noteBuffer(aNote)
for k, v in pairs (chordsList) do --this is supposed to block duplicates but it doesn't work correctly
if aNote == v then
return
end
end
if util.time() - timeLast < .6 then --window you have to input chords before reset
table.insert(chordsList, aNote) --script needs 8 notes for grid, adds octaves
if #chordsList < 8 then table.insert(chordsList, (aNote + 12)) end
if #chordsList < 8 then table.insert(chordsList, (aNote + 24)) end
else --starts new chords
print('Initializing buffer')
for k, v in pairs(chordsList) do chordsList[k]=nil end
table.insert(chordsList, aNote)
if #chordsList < 8 then table.insert(chordsList, (aNote + 12)) end --sometimes you need more octaves
while #chordsList < 8 do
for i=1,8 do
x = aNote + (i*12)
if x > 90 then if (x - 84) > 0 then x = x - 84 end end --adjusts range of octaves
table.insert(chordsList, x)
end
end
end
table.sort(chordsList) --sorts high to low
--duplicate detection that actually works but i don't understand why
local notehash = {}
local prunednotes = {}
for i,v in pairs(chordsList) do
if (not notehash[v]) then
prunednotes[#prunednotes+1] = v
notehash[v] = true
end
end
chordsList = prunednotes
capturedNotes = music.note_nums_to_names(chordsList) --for display
print(table.concat(capturedNotes, ' '))
for k, v in pairs(chordsList) do
print(k, v)
end
scale = chordsList
redraw()
timeLast = util.time()
end
m = midi.connect()
m.event = function(data)
local d = midi.to_msg(data)
if d.type == "note_on" then
noteBuffer(d.note)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment