| -- LA Drivers | |
| -- thanks to mark | |
| --enc 1: move key center by 5th | |
| --enc 2: move key center by 4th | |
| --key 2: walk through current scale | |
| --enc 3: add random/remove last | |
| --key 2: parallel modulation | |
| --key 3: random scale | |
| MusicUtil = require "mark_eats/musicutil" | |
| local UI = require "mark_eats/ui" | |
| local BeatClock = require "beatclock" | |
| local MollyThePoly = require "mark_eats/mollythepoly" | |
| engine.name = "MollyThePoly" | |
| local options = {} | |
| options.OUTPUT = {"Audio", "MIDI", "Audio + MIDI"} | |
| options.STEP_LENGTH_NAMES = {"1 bar", "1/2", "1/3", "1/4", "1/6", "1/8", "1/12", "1/16", "1/24", "1/32", "1/48", "1/64"} | |
| options.STEP_LENGTH_DIVIDERS = {1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64} | |
| local DATA_FILE_PATH = data_dir .. "mark_eats/loom.data" | |
| local SCREEN_FRAMERATE = 15 | |
| local screen_dirty = true | |
| local GRID_FRAMERATE = 30 | |
| local grid_dirty = true | |
| local grid_leds = {} | |
| local grid_w, grid_h = 16, 8 | |
| local beat_clock | |
| local grid_device | |
| local midi_in_device | |
| local midi_out_device | |
| local midi_out_channel | |
| local notes = {} | |
| local triggers = {} | |
| local custom_scale = false | |
| local scale_edit_id = 1 | |
| local scale_notes = {} | |
| local SCALES_LEN = #MusicUtil.SCALES | |
| local active_notes = {} | |
| local down_marks = {} | |
| local down_keys = {} | |
| local trails = {} | |
| local remove_animations = {} | |
| local DOWN_ANI_LENGTH = 0.2 | |
| local REMOVE_ANI_LENGTH = 0.4 | |
| local TRAIL_ANI_LENGTH = 6.0 | |
| local pages | |
| local tabs | |
| local playback_icon | |
| local add_remove_animations = {} | |
| local ADD_REMOVE_ANI_LENGTH = 0.2 | |
| local notes_changed_timeout = 0 | |
| local triggers_changed_timeout = 0 | |
| local NOTES_TRIGGERS_TIMEOUT_LENGTH = 0.2 | |
| local nextScale | |
| rootNote = math.random(12) | |
| gridScale = {} | |
| minorTonality = true | |
| tonality = 'Natural Minor' | |
| octave = 4 | |
| timeLast = util.time() | |
| transposeAmount = 0 | |
| function tonality_status () | |
| if minor_tonality then | |
| tonality = 'Natural Minor' | |
| elseif not minor_tonality then | |
| tonality = 'Major' | |
| end | |
| return tonality | |
| end | |
| function get_incoming_scale(s, t) --semitones to transpose, tonality, | |
| local transposeBy = s | |
| local scaleType = t | |
| local incomingScale = {} | |
| rootNote = (rootNote + transposeBy) % 12 | |
| incomingScale = MusicUtil.generate_scale_of_length(rootNote, scaleType, 128) | |
| return incomingScale | |
| end | |
| function update_grid_scale(s) --incoming scale | |
| local incomingScale = s | |
| local notesToKeep = {} | |
| local incomingGridScale = {} | |
| --get tone map of new pitches | |
| for k,v in pairs(masterScale) do | |
| local randomDir = math.random(2) | |
| for n_k, n_v in pairs(incomingScale) do | |
| if n_v == v then | |
| notesToKeep[k] = v | |
| --print('same', notesToKeep[k]) | |
| break | |
| elseif n_v ~= v then | |
| if randomDir > 1 then | |
| if n_v - 1 == v then | |
| notesToKeep[k] = v + 1 | |
| -- print('diff1', notesToKeep[k]) | |
| break | |
| end | |
| else | |
| if n_v + 1 == v then | |
| notesToKeep[k] = v - 1 | |
| -- print('diff2', notesToKeep[k]) | |
| break | |
| end | |
| end | |
| end | |
| end | |
| end | |
| --map scale to grid swap with place scale function | |
| for k,v in pairs(notesToKeep) do | |
| --print(k,v) | |
| if #incomingGridScale < 16 then | |
| if v >= gridScale[1] then | |
| incomingGridScale[#incomingGridScale + 1] = v | |
| end | |
| end | |
| end | |
| incomingGridScaledeDupe = duplicateCheck(incomingGridScale, incomingScale) | |
| -- | |
| masterScale = incomingScale | |
| return incomingGridScaledeDupe, incomingScale | |
| end | |
| function duplicateCheck(s,i_s) --scale, incoming scale | |
| local incomingGridScale = s | |
| local incomingScale = i_s | |
| local attemptCounter = 1 | |
| for k,v in pairs(incomingGridScale) do | |
| if incomingGridScale[k] ==incomingGridScale[k+1] then | |
| duplicate = true | |
| --print(k+1, incomingGridScale[k+1], incomingGridScale[k+2]) | |
| for a,b in pairs(incomingScale) do | |
| if incomingGridScale[k+1] == incomingScale[a] then | |
| incomingGridScale[k+1] = incomingScale[a+1] | |
| break | |
| end | |
| end | |
| end | |
| end | |
| --print('--------------') | |
| return incomingGridScale | |
| end | |
| function place_scale_on_grid(s) --scale | |
| local scaleBuilder = {} | |
| for k,v in pairs(s) do | |
| if #scaleBuilder < 16 then | |
| if v >= (12*octave) then | |
| scaleBuilder[#scaleBuilder + 1] = v | |
| end | |
| end | |
| end | |
| --for k,v in pairs(scaleBuilder) do print(k,v) end | |
| return scaleBuilder | |
| end | |
| function pivot_scale(s, p) --scale, pivot amount | |
| local toPiv = s | |
| local pivedScale = {} | |
| local pivAmount = p | |
| for k,v in pairs(toPiv) do | |
| for n_k, n_v in pairs(masterScale) do | |
| if v == n_v then | |
| if masterScale[n_k+p] then | |
| pivedScale[k] = masterScale[n_k+p] | |
| break | |
| else | |
| return toPiv | |
| end | |
| end | |
| end | |
| end | |
| return pivedScale | |
| end | |
| function enc(n, delta) | |
| local newScale | |
| if n == 1 then | |
| if delta > 0 then | |
| transposeAmount = 7 | |
| elseif delta < 0 then | |
| transposeAmount = -7 | |
| end | |
| if util.time() - timeLast > .1 then | |
| newScale, MasterScale = update_grid_scale(get_incoming_scale(transposeAmount, tonality)) | |
| gridScale = place_scale_on_grid(newScale) | |
| --print(MusicUtil.note_num_to_name(masterScale[1], true)) | |
| end | |
| elseif n == 2 then | |
| if not shiftA then | |
| if delta > 0 then | |
| transposeAmount = 5 | |
| elseif delta < 0 then | |
| transposeAmount = -5 | |
| end | |
| if util.time() - timeLast > .1 then | |
| newScale = update_grid_scale(get_incoming_scale(transposeAmount, tonality)) | |
| gridScale = place_scale_on_grid(newScale) | |
| end | |
| elseif shiftA then | |
| if delta > 0 then | |
| gridScale = pivot_scale(gridScale, 1) | |
| elseif delta < 0 then | |
| gridScale = pivot_scale(gridScale, -1) | |
| end | |
| end | |
| elseif n == 3 then | |
| if delta > 1 then | |
| add_random() | |
| elseif delta < 1 then | |
| remove_last() | |
| end | |
| end | |
| timeLast = util.time() | |
| redraw() | |
| end | |
| function key(n, z) | |
| if z == 1 then | |
| if n == 2 then | |
| shiftA = true | |
| aTime = util.time() | |
| elseif n == 3 then | |
| transposeAmount = math.random(12) - 6 | |
| newScale = update_grid_scale(get_incoming_scale(transposeAmount, tonality)) | |
| gridScale = place_scale_on_grid(newScale) | |
| end | |
| elseif z == 0 then | |
| if n == 2 then | |
| shiftA = false | |
| if util.time() - aTime < .5 then | |
| if tonality == 'Natural Minor' then | |
| tonality = 'Major' | |
| else | |
| tonality = 'Natural Minor' | |
| end | |
| transposeAmount = 0 | |
| newScale = update_grid_scale(get_incoming_scale(transposeAmount, tonality)) | |
| gridScale = place_scale_on_grid(newScale) | |
| end | |
| end | |
| end | |
| end | |
| --print(circle_position, key_name) | |
| ----------------- | |
| --[[ ]] | |
| local function note_on(note_num) | |
| local min_vel, max_vel = params:get("min_velocity"), params:get("max_velocity") | |
| if min_vel > max_vel then | |
| max_vel = min_vel | |
| end | |
| local note_midi_vel = math.random(min_vel, max_vel) | |
| -- print("note_on", note_num, note_midi_vel) | |
| -- Audio engine out | |
| if params:get("output") == 1 or params:get("output") == 3 then | |
| engine.noteOn(note_num, MusicUtil.note_num_to_freq(note_num), note_midi_vel / 127) | |
| end | |
| -- MIDI out | |
| if (params:get("output") == 2 or params:get("output") == 3) then | |
| midi_out_device.note_on(note_num, note_midi_vel, midi_out_channel) | |
| end | |
| end | |
| local function note_off(note_num) | |
| -- print("note_off", note_num) | |
| -- Audio engine out | |
| if params:get("output") == 1 or params:get("output") == 3 then | |
| engine.noteOff(note_num) | |
| end | |
| -- MIDI out | |
| if (params:get("output") == 2 or params:get("output") == 3) then | |
| midi_out_device.note_off(note_num, nil, midi_out_channel) | |
| end | |
| end | |
| local function all_notes_off() | |
| -- Audio engine out | |
| engine.noteKillAll() | |
| -- MIDI out | |
| if (params:get("output") == 2 or params:get("output") == 3) then | |
| for _, a in pairs(active_notes) do | |
| midi_out_device.note_off(a, 96, midi_out_channel) | |
| end | |
| end | |
| active_notes = {} | |
| end | |
| local function add_note(position, head, length, direction, show_screen_animation) | |
| length = length or 1 | |
| direction = direction or 1 | |
| local note = {position = position, head = head, length = length, advance_countdown = length, direction = direction, active = false} | |
| table.insert(notes, note) | |
| end | |
| local function add_trigger(position, head, length, direction, show_screen_animation) | |
| length = length or 1 | |
| direction = direction or 1 | |
| local trigger = {position = position, head = head, length = length, advance_countdown = length, direction = direction, active = false} | |
| table.insert(triggers, trigger) | |
| end | |
| local function remove_note(position, show_screen_animation, silent) | |
| local note | |
| if position then | |
| for k, v in pairs(notes) do | |
| if v.position == position then | |
| note = table.remove(notes, k) | |
| break | |
| end | |
| end | |
| else | |
| note = table.remove(notes) | |
| end | |
| if note and not silent then | |
| if show_screen_animation then | |
| else | |
| notes_changed_timeout = NOTES_TRIGGERS_TIMEOUT_LENGTH | |
| end | |
| grid_dirty = true | |
| end | |
| end | |
| local function remove_trigger(position, show_screen_animation, silent) | |
| local trigger | |
| if position then | |
| for k, v in pairs(triggers) do | |
| if v.position == position then | |
| trigger = table.remove(triggers, k) | |
| break | |
| end | |
| end | |
| else | |
| trigger = table.remove(triggers) | |
| end | |
| if trigger and not silent then | |
| if show_screen_animation then | |
| else | |
| triggers_changed_timeout = NOTES_TRIGGERS_TIMEOUT_LENGTH | |
| end | |
| grid_dirty = true | |
| end | |
| end | |
| function add_random() | |
| if #notes >= grid_w and #triggers >= grid_h then return end | |
| -- Note | |
| if math.random() >= 0.5 then | |
| local available_positions = {} | |
| for i = 1, grid_w do | |
| local available = true | |
| for _, vn in pairs(notes) do | |
| if vn.position == i then | |
| available = false | |
| break | |
| end | |
| end | |
| if available then | |
| table.insert(available_positions, i) | |
| end | |
| end | |
| if #available_positions > 0 then | |
| local length = util.round(math.pow(math.random(), 4) * (grid_h - 2) + 1) | |
| add_note(available_positions[math.random(#available_positions)], math.random(grid_h), length, (math.random() >= 0.5 and 1 or -1), true) | |
| end | |
| -- Trigger | |
| else | |
| local available_positions = {} | |
| for i = 1, grid_h do | |
| local available = true | |
| for _, vt in pairs(triggers) do | |
| if vt.position == i then | |
| available = false | |
| break | |
| end | |
| end | |
| if available then | |
| table.insert(available_positions, i) | |
| end | |
| end | |
| if #available_positions > 0 then | |
| local length = util.round(math.pow(math.random(), 4) * (grid_w - 2) + 1) | |
| add_trigger(available_positions[math.random(#available_positions)], math.random(grid_w), length, (math.random() >= 0.5 and 1 or -1), true) | |
| end | |
| end | |
| end | |
| function remove_last() | |
| if #notes == 0 and #triggers == 0 then return end | |
| if math.random() >= 0.5 then | |
| remove_note(nil, true) | |
| else | |
| remove_trigger(nil, true) | |
| end | |
| end | |
| local function advance_step() | |
| if grid_device then | |
| grid_w = grid_device.cols() | |
| grid_h = grid_device.rows() | |
| if grid_w ~= 8 and grid_w ~= 16 then grid_w = 16 end | |
| if grid_h ~= 8 and grid_h ~= 16 then grid_h = 8 end | |
| end | |
| for _, n in pairs(notes) do | |
| n.advance_countdown = n.advance_countdown - 1 | |
| if n.advance_countdown == 0 then | |
| n.advance_countdown = n.length | |
| if n.direction > 0 then n.head = n.head % params:get("pattern_height") + 1 | |
| else n.head = (n.head + params:get("pattern_height") - 2) % params:get("pattern_height") + 1 end | |
| end | |
| n.active = false | |
| end | |
| local active_notes_this_step = {} | |
| for _, t in pairs(triggers) do | |
| -- Progress | |
| t.advance_countdown = t.advance_countdown - 1 | |
| if t.advance_countdown == 0 then | |
| t.advance_countdown = t.length | |
| if t.direction > 0 then t.head = t.head % params:get("pattern_width") + 1 | |
| else t.head = (t.head + params:get("pattern_width") - 2) % params:get("pattern_width") + 1 end | |
| end | |
| t.active = false | |
| -- Check for intersections and generate trails | |
| local tx | |
| for ti = 0, t.length - 1 do | |
| tx = t.head + (ti * t.direction * -1) | |
| tx = (tx - 1) % params:get("pattern_width") + 1 | |
| if tx <= grid_w then trails[tx][t.position] = TRAIL_ANI_LENGTH end | |
| for _, n in pairs(notes) do | |
| local ny | |
| for ni = 0, n.length - 1 do | |
| ny = n.head + (ni * n.direction * -1) | |
| ny = (ny - 1) % params:get("pattern_height") + 1 | |
| if ny <= grid_h then trails[n.position][ny] = TRAIL_ANI_LENGTH end | |
| if tx == n.position and t.position == ny then | |
| if not n.active then | |
| table.insert(active_notes_this_step, gridScale[n.position]) | |
| end | |
| n.active = true | |
| t.active = true | |
| break | |
| end | |
| end | |
| end | |
| end | |
| end | |
| -- Generate trails for notes if need be | |
| if #triggers == 0 then | |
| for _, n in pairs(notes) do | |
| local ny | |
| for ni = 0, n.length - 1 do | |
| ny = n.head + (ni * n.direction * -1) | |
| ny = (ny - 1) % params:get("pattern_height") + 1 | |
| if ny <= grid_h then trails[n.position][ny] = TRAIL_ANI_LENGTH end | |
| end | |
| end | |
| end | |
| -- Work out which need noteOffs | |
| for i = #active_notes, 1, -1 do | |
| local still_active = false | |
| for sk, sa in pairs(active_notes_this_step) do | |
| if sa == active_notes[i] then | |
| still_active = true | |
| table.remove(active_notes_this_step, sk) | |
| break | |
| end | |
| end | |
| if not still_active then | |
| note_off(active_notes[i]) | |
| table.remove(active_notes, i) | |
| end | |
| end | |
| -- Add remaining, the new notes | |
| for _, sa in pairs(active_notes_this_step) do | |
| if #active_notes < params:get("max_active_notes") then | |
| note_on(sa) | |
| table.insert(active_notes, sa) | |
| end | |
| end | |
| screen_dirty = true | |
| grid_dirty = true | |
| end | |
| local function reset_step() | |
| beat_clock:reset() | |
| end | |
| local function grid_update() | |
| if #down_marks > 0 or #remove_animations > 0 then grid_dirty = true end | |
| local time_increment = 1 / GRID_FRAMERATE | |
| -- Trails | |
| for x = 1, grid_w do | |
| for y = 1, grid_h do | |
| trails[x][y] = util.clamp(trails[x][y] - time_increment, 0, TRAIL_ANI_LENGTH) | |
| if trails[x][y] > 0 then grid_dirty = true end | |
| end | |
| end | |
| -- Down marks | |
| for i = #down_marks, 1, -1 do | |
| if not down_marks[i].active then | |
| down_marks[i].time_remaining = down_marks[i].time_remaining - time_increment | |
| if down_marks[i].time_remaining <= 0 then | |
| table.remove(down_marks, i) | |
| end | |
| end | |
| end | |
| -- Remove animations | |
| for i = #remove_animations, 1, -1 do | |
| remove_animations[i].time_remaining = remove_animations[i].time_remaining - time_increment | |
| if remove_animations[i].time_remaining <= 0 then | |
| table.remove(remove_animations, i) | |
| end | |
| end | |
| end | |
| function grid_event(x, y, z) | |
| if z == 1 then | |
| -- Is there a relevant down mark? | |
| local relevant_down_mark = nil | |
| for k, v in pairs(down_marks) do | |
| -- Re-activate fading down mark | |
| if v.x == x and v.y == y then | |
| v.active = true | |
| v.time_remaining = DOWN_ANI_LENGTH | |
| relevant_down_mark = v | |
| break | |
| -- Note | |
| elseif v.x == x and v.active then | |
| local three_keys = false | |
| for _, kv in pairs(down_keys) do | |
| if kv.x == x then | |
| three_keys = true | |
| break | |
| end | |
| end | |
| if three_keys then | |
| remove_note(x, true, false) | |
| else | |
| remove_note(x, false, true) | |
| add_note(x, v.y, math.abs(v.y - y), (y < v.y and 1 or -1), false) | |
| end | |
| relevant_down_mark = v | |
| -- Trigger | |
| elseif v.y == y and v.active then | |
| local three_keys = false | |
| for _, kv in pairs(down_keys) do | |
| if kv.y == y then | |
| three_keys = true | |
| break | |
| end | |
| end | |
| if three_keys then | |
| remove_trigger(y, true, false) | |
| else | |
| remove_trigger(y, false, true) | |
| add_trigger(y, v.x, math.abs(v.x - x), (x < v.x and 1 or -1), false) | |
| end | |
| relevant_down_mark = v | |
| end | |
| end | |
| -- Make it the down mark | |
| if relevant_down_mark then | |
| if relevant_down_mark.x ~= x or relevant_down_mark.y ~= y then | |
| table.insert(down_keys, {x = x, y = y}) | |
| end | |
| else | |
| table.insert(down_marks, {active = true, x = x, y = y, time_remaining = DOWN_ANI_LENGTH}) | |
| end | |
| else | |
| for _, v in pairs(down_marks) do | |
| if v.x == x and v.y == y then | |
| v.active = false | |
| break | |
| end | |
| end | |
| for k, v in pairs(down_keys) do | |
| if v.x == x and v.y == y then | |
| table.remove(down_keys, k) | |
| break | |
| end | |
| end | |
| end | |
| grid_dirty = true | |
| end | |
| function init() | |
| --me | |
| masterScale = MusicUtil.generate_scale_of_length(rootNote, tonality, 128) | |
| gridScale = place_scale_on_grid(masterScale) | |
| --og | |
| for x = 1, 16 do | |
| grid_leds[x] = {} | |
| trails[x] = {} | |
| for y = 1, 16 do | |
| trails[x][y] = 0 | |
| end | |
| end | |
| grid_device = grid.connect(1) | |
| grid_device.event = grid_event | |
| beat_clock = BeatClock.new() | |
| beat_clock.on_step = advance_step | |
| beat_clock.on_select_internal = function() | |
| beat_clock:start() | |
| screen_dirty = true | |
| end | |
| beat_clock.on_select_external = function() | |
| reset_step() | |
| screen_dirty = true | |
| end | |
| midi_in_device = midi.connect(1) | |
| midi_in_device.event = function(data) | |
| beat_clock:process_midi(data) | |
| if not beat_clock.playing and playback_icon.status == 1 then | |
| screen_dirty = true | |
| end | |
| end | |
| midi_out_device = midi.connect(1) | |
| midi_out_device.event = function() end | |
| local screen_refresh_metro = metro.alloc() | |
| screen_refresh_metro.callback = function() | |
| if screen_dirty then | |
| screen_dirty = false | |
| redraw() | |
| end | |
| end | |
| local grid_redraw_metro = metro.alloc() | |
| grid_redraw_metro.callback = function() | |
| grid_update() | |
| if grid_dirty and grid_device.attached() then | |
| grid_dirty = false | |
| grid_redraw() | |
| end | |
| end | |
| -- Add params | |
| params:add{type = "number", id = "grid_device", name = "Grid Device", min = 1, max = 4, default = 1, action = function(value) | |
| grid_device.all(0) | |
| grid_device.refresh() | |
| grid_device:reconnect(value) | |
| end} | |
| params:add{type = "option", id = "output", name = "Output", options = options.OUTPUT, action = all_notes_off} | |
| params:add{type = "number", id = "midi_out_device", name = "MIDI Out Device", min = 1, max = 4, default = 1, action = function(value) | |
| midi_out_device:reconnect(value) | |
| end} | |
| params:add{type = "number", id = "midi_out_channel", name = "MIDI Out Channel", min = 1, max = 16, default = 1, action = function(value) | |
| all_notes_off() | |
| midi_out_channel = value | |
| end} | |
| params:add{type = "number", id = "max_active_notes", name = "Max Active Notes", min = 1, max = 16, default = 16} | |
| params:add{type = "option", id = "clock", name = "Clock", options = {"Internal", "External"}, default = beat_clock.external or 2 and 1, action = function(value) | |
| beat_clock:clock_source_change(value) | |
| end} | |
| params:add{type = "number", id = "clock_midi_in_device", name = "Clock MIDI In Device", min = 1, max = 4, default = 1, action = function(value) | |
| midi_in_device:reconnect(value) | |
| end} | |
| params:add{type = "option", id = "clock_out", name = "Clock Out", options = {"Off", "On"}, default = beat_clock.send or 2 and 1, action = function(value) | |
| if value == 1 then beat_clock.send = false | |
| else beat_clock.send = true end | |
| end} | |
| params:add_separator() | |
| params:add{type = "number", id = "bpm", name = "BPM", min = 1, max = 480, default = 140, action = function(value) | |
| beat_clock:bpm_change(value) | |
| screen_dirty = true | |
| end} | |
| params:add{type = "option", id = "step_length", name = "Step Length", options = options.STEP_LENGTH_NAMES, default = 8, action = function(value) | |
| beat_clock.steps_per_beat = options.STEP_LENGTH_DIVIDERS[value] / 4 | |
| beat_clock:bpm_change(beat_clock.bpm) | |
| end} | |
| params:add{type = "number", id = "pattern_width", name = "Pattern Width", min = 8, max = 64, default = 16} | |
| params:add{type = "number", id = "pattern_height", name = "Pattern Height", min = 8, max = 64, default = 8} | |
| params:add{type = "number", id = "min_velocity", name = "Min Velocity", min = 1, max = 127, default = 80} | |
| params:add{type = "number", id = "max_velocity", name = "Max Velocity", min = 1, max = 127, default = 100} | |
| params:add_separator() | |
| midi_out_channel = params:get("midi_out_channel") | |
| -- Engine params | |
| MollyThePoly.add_params() | |
| -- UI | |
| screen.aa(1) | |
| screen_refresh_metro:start(1 / SCREEN_FRAMERATE) | |
| grid_redraw_metro:start(1 / GRID_FRAMERATE) | |
| beat_clock:start() | |
| end | |
| function grid_redraw() | |
| local DOWN_BRIGHTNESS = 1 | |
| local TRAIL_BRIGHTNESS = 1 | |
| local OUTSIDE_BRIGHTNESS = 1 | |
| local INACTIVE_BRIGHTNESS = 3 | |
| local ACTIVE_BRIGHTNESS = 12 | |
| local brightness | |
| -- Draw trails | |
| for x = 1, 16 do | |
| for y = 1, 16 do | |
| if trails[x][y] then grid_leds[x][y] = util.round(util.linlin(0, TRAIL_ANI_LENGTH, 0, TRAIL_BRIGHTNESS, trails[x][y])) | |
| else grid_leds[x][y] = 0 end | |
| if (x > params:get("pattern_width") or y > params:get("pattern_height")) and grid_leds[x][y] < OUTSIDE_BRIGHTNESS then grid_leds[x][y] = OUTSIDE_BRIGHTNESS end | |
| end | |
| end | |
| -- Draw down marks | |
| for k, v in pairs(down_marks) do | |
| brightness = util.round(util.linlin(0, DOWN_ANI_LENGTH, 0, DOWN_BRIGHTNESS, v.time_remaining)) | |
| for i = 1, grid_w do | |
| if grid_leds[i][v.y] < brightness then grid_leds[i][v.y] = brightness end | |
| end | |
| for i = 1, grid_h do | |
| if grid_leds[v.x][i] < brightness then grid_leds[v.x][i] = brightness end | |
| end | |
| if v.active and grid_leds[v.x][v.y] < INACTIVE_BRIGHTNESS then grid_leds[v.x][v.y] = INACTIVE_BRIGHTNESS end | |
| end | |
| -- Draw remove animations | |
| for _, v in pairs(remove_animations) do | |
| brightness = util.round(util.linlin(0, REMOVE_ANI_LENGTH, 0, 15, v.time_remaining)) | |
| if v.orientation == "row" then | |
| for i = 1, grid_w do | |
| if grid_leds[i][v.position] < brightness then grid_leds[i][v.position] = brightness end | |
| end | |
| else | |
| for i = 1, grid_h do | |
| if grid_leds[v.position][i] < brightness then grid_leds[v.position][i] = brightness end | |
| end | |
| end | |
| end | |
| -- Draw notes | |
| for _, n in pairs(notes) do | |
| if n.active then brightness = ACTIVE_BRIGHTNESS | |
| else brightness = INACTIVE_BRIGHTNESS end | |
| if n.position <= grid_w then | |
| local ny | |
| for i = 0, n.length - 1 do | |
| ny = n.head + (i * n.direction * -1) | |
| ny = (ny - 1) % params:get("pattern_height") + 1 | |
| if ny > 0 and ny <= grid_h then | |
| grid_leds[n.position][ny] = brightness | |
| end | |
| end | |
| end | |
| end | |
| -- Draw triggers | |
| for _, t in pairs(triggers) do | |
| if t.active then brightness = ACTIVE_BRIGHTNESS | |
| else brightness = INACTIVE_BRIGHTNESS end | |
| if t.position <= grid_h then | |
| local tx | |
| for i = 0, t.length - 1 do | |
| tx = t.head + (i * t.direction * -1) | |
| tx = (tx - 1) % params:get("pattern_width") + 1 | |
| if tx > 0 and tx <= grid_w then | |
| grid_leds[tx][t.position] = brightness | |
| end | |
| end | |
| end | |
| end | |
| for x = 1, grid_w do | |
| for y = 1, grid_h do | |
| grid_device.led(x, y, grid_leds[x][y]) | |
| end | |
| end | |
| grid_device.refresh() | |
| end | |
| function redraw() | |
| screen.clear() | |
| -- Scale name | |
| screen.move(5, 10) | |
| screen.level(15) | |
| screen.text(MusicUtil.note_num_to_name(rootNote) .. " " .. tonality) | |
| -- Scale notes | |
| local x, y = 5, 14 | |
| local scale_note_names = MusicUtil.note_nums_to_names(gridScale, true) | |
| local COLS = 4 | |
| if #scale_note_names ~= 16 then | |
| --print (#scale_note_names) | |
| else | |
| for i = 1, grid_w do | |
| if (i - 1) % COLS == 0 then x, y = 5, y + 11 end | |
| local is_active = false | |
| for _, n in pairs(notes) do | |
| if n.position == i and n.active then | |
| is_active = true | |
| break | |
| end | |
| end | |
| local underline_length = 10 | |
| if string.len(scale_note_names[i]) > 3 then | |
| underline_length = 18 | |
| elseif string.len(scale_note_names[i]) > 2 then | |
| underline_length = 16 | |
| end | |
| if custom_scale and i == scale_edit_id then | |
| screen.level(15) | |
| screen.move(x - 1, y + 2.5) | |
| screen.line(x + underline_length, y + 2.5) | |
| screen.stroke() | |
| end | |
| if is_active then screen.level(15) | |
| else screen.level(3) end | |
| screen.move(x, y) | |
| screen.text(scale_note_names[i]) | |
| x = x + 25 | |
| end | |
| end | |
| screen.update() | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment