| -- 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