Skip to content

Instantly share code, notes, and snippets.

@ypxk
Last active January 15, 2019 04:31
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/e1d204a7781589ee76d61154b860f253 to your computer and use it in GitHub Desktop.
Save ypxk/e1d204a7781589ee76d61154b860f253 to your computer and use it in GitHub Desktop.
-- los angeles traffic
--
-- enc 1: circle of 5ths
-- enc 2: jump notes
-- enc 3: add/remove
-- key 2 (tap): major/minor
-- key 2 (hold) + enc 2:
-- jump amount
-- key 2 (hold) + enc 3:
-- amount of traffic
-- key 3 (tap):
-- record pattern,
-- play pattern,
-- (hold) clear pattern
--
-- traffic amounts:
-- sunday - normal
-- rush hour - slow
-- gridlock - stopped
--
--
--
--
--
--
--
--
--
-- slow down
MusicUtil = require "mark_eats/musicutil"
local BeatClock = require "beatclock"
local MollyThePoly = require "mark_eats/mollythepoly"
local pattern_time = require 'pattern_time'
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}
options.traffic_length_NAMES = {'sunday','rush hour', 'grid lock', 'reverse commute'}
options.traffic_length_DIVIDERS = {1, 2, 3, 4}
local triggerDuration
local gridDevice
local GRID_FRAMERATE = 30
local SCREEN_FRAMERATE = 15
local TRAIL_ANI_LENGTH = 6.0
local DOWN_ANI_LENGTH = 0.2
local gridDirty = true
local gridLEDs = {}
local trails = {}
local downMarks = {}
local downKeys = {}
local removeAnimations = {}
local grid_w, grid_h = 16, 8
local notes = {}
local triggers = {}
local activeNotes = {}
local stepDuration
local stepsPerBar
local tonality = 'Natural Minor'
local rootNote = math.random(12)
local octave = rootNote + (4*12)
local masterScale
local gridScale = {}
local newGridScale = {}
local timeLast = util.time()
local currentScale
local stepsPerBar
local currentStep = 0
local bar = 0
local transposeAmount
local pivotAmount = 1
local clearBuffer
local progression = {}
progression.isplaying = false
local patternClearer
local prevTranspose = 60
m = midi.connect()
function pitch_quantizer(n)
local negative
if n < 0 then negative = true end
n = math.abs(n)
if n == 0 then return n end
if n == 1 then n = 1 end
if n == 2 then n = 1
elseif n == 03 then n = 2
elseif n == 04 then n = 2
elseif n == 05 then n = 3
elseif n == 06 then n = 3
elseif n == 07 then n = 4
elseif n == 08 then n = 4
elseif n == 09 then n = 5
elseif n == 10 then n = 6
elseif n == 11 then n = 6
elseif n == 12 then n = 7
elseif n == 13 then n = 7
elseif n == 14 then n = 1 + 7
elseif n == 15 then n = 2 + 7
elseif n == 16 then n = 2 + 7
elseif n == 17 then n = 3 + 7
elseif n == 18 then n = 3 + 7
elseif n == 19 then n = 4 + 7
elseif n == 20 then n = 4 + 7
elseif n == 21 then n = 5 + 7
elseif n == 22 then n = 6 + 7
elseif n == 23 then n = 6 + 7
elseif n == 24 then n = 7 + 7
end
if negative then n = -(n) end
return n
end
--for pattern
function get_bar_length()
if stepDuration == 1 then --1 bar
stepsPerBar = 1
elseif stepDuration == 2 then -- "1/2"
stepsPerBar = 2
elseif stepDuration == 3 then -- "1/3",
stepsPerBar = 3
elseif stepDuration == 4 then -- "1/4",
stepsPerBar = 4
elseif stepDuration == 5 then -- "1/6",
stepsPerBar = 6
elseif stepDuration == 6 then -- "1/8",
stepsPerBar = 8
elseif stepDuration == 7 then -- "1/12",
stepsPerBar = 12
elseif stepDuration == 8 then -- "1/16",
stepsPerBar = 16
elseif stepDuration == 9 then -- "1/24",
stepsPerBar = 24
elseif stepDuration == 10 then -- "1/32",
stepsPerBar = 32
elseif stepDuration == 11 then -- "1/48",
stepsPerBar = 48
elseif stepDuration == 12 then -- "1/64"}
stepsPerBar = 64
end
return stepsPerBar
end
function change_tonality()
if tonality == 'Natural Minor' then
tonality = 'Major'
else
tonality = 'Natural Minor'
end
change_scale(0, tonality)
end
function background_change_tonality()
if progression.tonality == 'Natural Minor' then
progression.tonality = 'Major'
else
progression.tonality = 'Natural Minor'
end
background_change_scale(0, progression.tonality)
end
function set_octave(newOctave)
octave = rootNote + (newOctave * 12)
end
function capture_scale()
-- print('capturing', MusicUtil.note_num_to_name(gridScale[1]))
progression.gridscale = {}
progression.masterscale = {}
progression.tonality = tonality
progression.rootnote = rootNote
progression.prevtranspose = prevTranspose
for k,v in pairs(gridScale) do
progression.gridscale[k] = v
end
for k,v in pairs(masterScale) do
progression.masterscale[k] = v
end
end
function change_scale(semitones,scaleType,clockwisemotion)
local incomingScale = {}
local notesToKeep = {}
local incomingGridScale = {}
local newRoot
clockwisemotion = clockwisemotion or 1
if semitones == 0 and scaleType == tonality then return end
newRoot = (rootNote + semitones) % 12
incomingScale = MusicUtil.generate_scale_of_length(newRoot, scaleType, 128)
--get tone map of new pitches
for k,v in pairs(masterScale) do
for n_k, n_v in pairs(incomingScale) do
if n_v == v then
notesToKeep[k] = v
break
elseif n_v ~= v then
if n_v - 1 == v then
notesToKeep[k] = v + 1
break
end
else
if n_v + 1 == v then
notesToKeep[k] = v - 1
break
end
end
end
end
--map scale to grid
for k,v in pairs(notesToKeep) do
if #incomingGridScale < 16 then
if clockwisemotion < 0 then
if v >= (gridScale[1] - 1) then
incomingGridScale[#incomingGridScale + 1] = v
end
elseif clockwisemotion > 0 then
if v >= (gridScale[1]) then
incomingGridScale[#incomingGridScale + 1] = v
end
end
elseif #incomingGridScale == 16 then
break
end
end
--detect duplicates
for k,v in pairs(incomingGridScale) do
if incomingGridScale [k] == incomingGridScale[k+1] then
for a, b in pairs(incomingScale) do
if v == b then
incomingGridScale[k+1] = incomingScale[a+1]
break
end
end
end
end
gridScale = incomingGridScale
rootNote = newRoot
masterScale = incomingScale
screenDirty = true
end
function background_change_scale(semitones,scaleType,clockwisemotion)
local incomingScale = {}
local notesToKeep = {}
local incomingGridScale = {}
local newRoot
clockwisemotion = clockwisemotion or 1
newRoot = (progression.rootnote + semitones) % 12
incomingScale = MusicUtil.generate_scale_of_length(newRoot, scaleType, 128)
--get tone map of new pitches
for k,v in pairs(progression.masterscale) do
for n_k, n_v in pairs(incomingScale) do
if n_v == v then
notesToKeep[k] = v
break
elseif n_v ~= v then
if n_v - 1 == v then
notesToKeep[k] = v + 1
break
end
else
if n_v + 1 == v then
notesToKeep[k] = v - 1
break
end
end
end
end
--map scale to grid
for k,v in pairs(notesToKeep) do
if #incomingGridScale < 16 then
if clockwisemotion < 0 then
if v >= (gridScale[1] - 1) then
incomingGridScale[#incomingGridScale + 1] = v
end
elseif clockwisemotion > 0 then
if v >= (gridScale[1]) then
incomingGridScale[#incomingGridScale + 1] = v
end
end
elseif #incomingGridScale == 16 then
break
end
end
--detect duplicates
for k,v in pairs(incomingGridScale) do
if incomingGridScale [k] == incomingGridScale[k+1] then
for a, b in pairs(incomingScale) do
if v == b then
incomingGridScale[k+1] = incomingScale[a+1]
break
end
end
end
end
progression.gridscale = incomingGridScale
progression.rootnote = newRoot
progression.masterscale = incomingScale
end
function pivot_within_scale(pivot)
if pivot == 0 then return end
local counter = 0
for mk, mv in pairs(masterScale) do
if mv == gridScale[1] then
for gk,gv in pairs(gridScale) do
if not masterScale[mk + pivot + counter] then return end
gridScale[gk] = masterScale[mk + pivot + counter]
counter = counter + 1
end
break
end
end
end
function background_pivot_within_scale(pivot)
local counter = 1
for mk, mv in pairs(progression.masterscale) do
if mv >= progression.gridscale[1] then
if not progression.masterscale[mk + pivot] then return end
progression.gridscale[counter] = progression.masterscale[mk + pivot]
counter = counter + 1
end
if counter == 17 then
counter = 1
break
end
end
end
function play_progression(e)
if pat.step <= 1 then
prevTranspose = progression.prevtranspose
for k,v in pairs(progression.gridscale) do
gridScale[k] = v
end
--print('resetting to captured', MusicUtil.note_num_to_name(gridScale[1]))
for k,v in pairs(progression.masterscale) do
masterScale[k] = v
end
tonality = progression.tonality
rootNote = progression.rootnote
screenDirty = true
end
if e then
--print(pat.step, e.id, e.number)
if e.id == 'key' then
key_event(e.number, e.state)
elseif e.id == 'enc' then
enc_event(e.number, e.state)
elseif e.id == 'midi' then
midi_event(e.number)
end
end
end
function enc_event(n, delta)
--enc 1
if n == 1 then
if delta > 0 then
transposeAmount = 7
elseif delta < 0 then
transposeAmount = -7
end
if util.time() - timeLast > .1 then
change_scale(transposeAmount, tonality,delta)
end
--encoder 2
elseif n == 2 then
local prevPivot = pivotAmount
if delta > 0 then
pivotAmount = math.abs(pivotAmount)
elseif delta < 0 then
pivotAmount = pivotAmount * -1
end
if util.time() - timeLast > .1 then pivot_within_scale(pivotAmount) end
pivotAmount = prevPivot
if pivotAmount <= 0 then pivotAmount = 7 end
--encoder 3
elseif n == 3 then
if delta > 0 then
elseif delta < 0 then
end
end
timeLast = util.time()
end
function key_event(n, z)
if z == 1 then
clearBuffer = util.time()
end
--key 1
if n == 1 then
elseif n == 2 then
if z == 0 then
if util.time() - clearBuffer < .5 then
change_tonality()
end
end
--key 3
elseif n == 3 then
end
end
function midi_event(data)
local note
if data ~= prevTranspose then
note = pitch_quantizer(data - prevTranspose)
if note then pivot_within_scale(note) end
end
prevTranspose = data
end
function m.event(data)
local d = midi.to_msg(data)
local note
local e = {}
if d.type == "note_on" then
e.id = 'midi'
e.number = d.note
pat:watch(e)
if d.note ~= prevTranspose then
note = pitch_quantizer(d.note - prevTranspose)
if note then pivot_within_scale(note) end
end
prevTranspose = d.note
end
end
function enc(n, delta)
local e = {}
e.id = 'enc'
e.number = n
e.state = delta
pat:watch(e)
--enc 1
if n == 1 then
if delta > 0 then transposeAmount = 7
elseif delta < 0 then transposeAmount = -7 end
if util.time() - timeLast > .1 then
if not progression.isplaying then
change_scale(transposeAmount, tonality,delta)
else
background_change_scale(transposeAmount, tonality,delta)
end
end
--encoder 2
elseif n == 2 then
if not shiftA then
local prevPivot = pivotAmount
if delta > 0 then pivotAmount = math.abs(pivotAmount)
elseif delta < 0 then pivotAmount = pivotAmount * -1 end
if util.time() - timeLast > .1 then
if not progression.isplaying then
pivot_within_scale(pivotAmount)
else
background_pivot_within_scale(pivotAmount)
end
end
pivotAmount = prevPivot
elseif shiftA then
--prevPivot = pivotAmount
if delta > 0 then
if util.time() - timeLast > .1 then pivotAmount = pivotAmount%7+delta end
elseif delta < 0 then
if util.time() - timeLast > .1 then pivotAmount = pivotAmount%9+delta end
end
if pivotAmount <= 0 then pivotAmount = 7 end
end
--encoder 3
elseif n == 3 then
if not shiftA then
if delta > 0 then
add_random()
elseif delta < 0 then
remove_last()
end
else
local x = params:get("traffic_length")
if util.time() - timeLast > .1 then
x = x + delta
--print('x + ', delta, ' = ', x)
if x > 4 then x = 1
elseif x < (-2) then x = (-1) end
params:set("traffic_length", x)
end
end
end
timeLast = util.time()
end
function key(n, z)
if z == 1 then
clearBuffer = util.time()
end
--key 2
if n == 2 then
local e = {}
e.id = 'key'
e.number = n
e.state = z
--print(e.id, e.number, e.state)
pat:watch(e)
if z == 1 then shiftA = true end
if z == 0 then
shiftA = false
if util.time() - clearBuffer < .5 then
if not progression.isplaying then
change_tonality()
else
background_change_tonality()
end
end
end
--key 3
elseif n == 3 then
if z == 1 then
patternClearer = true
if pat.rec == 0 then
if not progression.isplaying then
capture_scale()
pat:stop()
pat:clear()
pat:rec_start()
end
elseif pat.rec == 1 then
pat:rec_stop()
pat:start()
if pat.count > 0 then progression.isplaying = true end
end
elseif z == 0 then
patternClearer = false
if pat.rec == 1 then
local e = {}
e.id = 'key'
e.number = n
e.state = z
--print(e.id, e.number, e.state)
pat:watch(e)
end
if util.time() - clearBuffer > 1 then
pat:clear()
print('cleared')
progression.isplaying = false
end
end
end
end
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
function note_off(noteNum)
if params:get("output") == 1 or params:get("output") == 3 then
engine.noteOff(noteNum)
end
if (params:get("output") == 2 or params:get("output") == 3) then
midi_out_device.note_off(noteNum, nil, midi_out_channel)
end
end
function all_notes_kill()
engine.noteKillAll()
if (params:get("output") == 2 or params:get("output") == 3) then
for _, a in pairs(activeNotes) do
midi_out_device.note_off(a, 96, midi_out_channel)
end
end
activeNotes = {}
end
function add_note(position, head, length, direction)
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)
gridDirty = true
end
function add_trigger(position, head, length, direction)
local triggerDuration
if params:get("traffic_length") == 1 then
length = length or 1
direction = direction or 1
elseif params:get("traffic_length") == 2 then
length = (length or 1) + 1
direction = direction or 1
elseif params:get("traffic_length") == 4 then
length = length or 1
direction = -1 * (direction or 1)
end
local trigger = {position = position, head = head, length = length, advance_countdown = length, direction = direction, active = false}
table.insert(triggers, trigger)
gridDirty = true
end
function remove_note(position, 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
gridDirty = true
end
end
function remove_trigger(position, 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
gridDirty = true
end
end
function add_random()
if #notes >= grid_w and #triggers >= grid_h then return end
if math.random() >= .5 then
local availablePositions = {}
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(availablePositions, i)
end
end
if #availablePositions > 0 then
local length = util.round(math.pow(math.random(), 4) * (grid_h - 2) + 1)
add_note(availablePositions[math.random(#availablePositions)],
math.random(grid_h),
length,
math.random() >= 0.5 and 1 or -1)
end
else
local availablePositions = {}
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(availablePositions, i)
end
end
if #availablePositions > 0 then
local length = util.round(math.pow(math.random(), 4) * (grid_w - 2) + 1)
add_trigger(availablePositions[math.random(#availablePositions)], 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() >= .5 then
remove_note(nil, true)
else
remove_trigger(nil, true)
end
end
function advance_step()
currentStep = currentStep + 1
if currentStep >= stepsPerBar then
bar = bar + 1
currentStep = 0
--pivot_within_scale(math.random(-2,2),bar)
end
if gridDevice then
grid_w = gridDevice.cols()
grid_h = gridDevice.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 activeNotesThisStep = {}
for _, t in pairs(triggers) do
-- Progress
if params:get("traffic_length") == 3 then
t.advance_countdown = t.advance_countdown
else
t.advance_countdown = t.advance_countdown - 1
end
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(activeNotesThisStep, 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 = #activeNotes, 1, -1 do
local still_active = false
for sk, sa in pairs(activeNotesThisStep) do
if sa == activeNotes[i] then
still_active = true
table.remove(activeNotesThisStep, sk)
break
end
end
if not still_active then
note_off(activeNotes[i])
table.remove(activeNotes, i)
end
end
-- Add remaining, the new notes
for _, sa in pairs(activeNotesThisStep) do
if #activeNotes < params:get("max_active_notes") then
note_on(sa)
table.insert(activeNotes, sa)
end
end
screenDirty = true
gridDirty = true
end
local function grid_update()
if #downMarks > 0 or #removeAnimations > 0 then gridDirty = 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 gridDirty = true end
end
end
-- Down marks
for i = #downMarks, 1, -1 do
if not downMarks[i].active then
downMarks[i].time_remaining = downMarks[i].time_remaining - time_increment
if downMarks[i].time_remaining <= 0 then
table.remove(downMarks, i)
end
end
end
-- Remove animations
for i = #removeAnimations, 1, -1 do
removeAnimations[i].time_remaining = removeAnimations[i].time_remaining - time_increment
if removeAnimations[i].time_remaining <= 0 then
table.remove(removeAnimations, i)
end
end
end
local function stop()
all_notes_kill()
end
local function reset_step()
beat_clock:reset()
end
local function grid_update()
if #downMarks > 0 then gridDirty = 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 gridDirty = true end
end
end
-- Down marks
for i = #downMarks, 1, -1 do
if not downMarks[i].active then
downMarks[i].time_remaining = downMarks[i].time_remaining - time_increment
if downMarks[i].time_remaining <= 0 then
table.remove(downMarks, i)
end
end
end
end
function init()
masterScale = MusicUtil.generate_scale_of_length(rootNote, tonality, 128)
--set first scale
for _, v in pairs(masterScale) do
if v >= octave then
gridScale[#gridScale + 1] = v
end
if #gridScale > 15 then break end
end
for x = 1, 16 do
gridLEDs[x] = {}
trails[x] = {}
for y = 1, 16 do
gridLEDs[x][y] = 0
trails[x][y] = 0
end
end
capture_scale()
gridDevice = grid.connect(1)
gridDevice.event = grid_event
beat_clock = BeatClock.new()
beat_clock.on_step = advance_step
beat_clock.on_stop = stop
beat_clock.on_select_internal = function()
beat_clock:start()
screenDirty = true
end
beat_clock.on_select_external = function()
reset_step()
screenDirty = true
end
midi_in_device = midi.connect(1)
midi_in_device.event = function(data)
beat_clock:process_midi(data)
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 screenDirty then
screenDirty = false
redraw()
end
end
local grid_redraw_metro = metro.alloc()
grid_redraw_metro.callback = function()
grid_update()
if gridDirty and gridDevice.attached() then
gridDirty = false
grid_redraw()
end
end
-- Add params
params:add{type = "number", id = "gridDevice", name = "Grid Device", min = 1, max = 4, default = 1,
action = function(value)
gridDevice.all(0)
gridDevice.refresh()
gridDevice:reconnect(value)
end}
params:add{type = "option", id = "output", name = "Output", options = options.OUTPUT, action = all_notes_kill}
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_kill()
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 = 340, default =140,
action = function(value)
beat_clock:bpm_change(value)
end}
params:add{type = "option",
id = "step_length",
name = "Step Length",
options = options.STEP_LENGTH_NAMES,
default = 8,
action = function(value)
stepDuration = value
stepsPerBar = get_bar_length()
beat_clock.steps_per_beat = options.STEP_LENGTH_DIVIDERS[value] / 4
beat_clock:bpm_change(beat_clock.bpm)
end}
params:add{type = "option",
id = "traffic_length",
name = "Traffic",
options = options.traffic_length_NAMES,
default = 1,
action = function(value)
triggerDuration = value
end}
params:add{type = "number", id = "pattern_width", name = "Pattern Width", min = 4, max = 64, default = 16,
action = function()
gridDirty = true
end}
params:add{type = "number", id = "pattern_height", name = "Pattern Height", min = 4, max = 64, default = 8,
action = function()
gridDirty = true
end}
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()
grid_redraw_metro:start(1 / GRID_FRAMERATE)
screen_refresh_metro:start(1 / SCREEN_FRAMERATE)
beat_clock:start()
pat = pattern_time.new()
pat.process = play_progression
end
function grid_event(x, y, z)
if z == 1 then
-- Is there a relevant down mark?
local relevantDownMark = nil
for k, v in pairs(downMarks) do
-- Re-activate fading down mark
if v.x == x and v.y == y then
v.active = true
v.time_remaining = DOWN_ANI_LENGTH
relevantDownMark = v
break
-- Note
elseif v.x == x and v.active then
local threeKeys = false
for _, kv in pairs(downKeys) do
if kv.x == x then
threeKeys = true
break
end
end
if threeKeys 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
relevantDownMark = v
-- Trigger
elseif v.y == y and v.active then
local threeKeys = false
for _, kv in pairs(downKeys) do
if kv.y == y then
threeKeys = true
break
end
end
if threeKeys 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
relevantDownMark = v
end
end
-- Make it the down mark
if relevantDownMark then
if relevantDownMark.x ~= x or relevantDownMark.y ~= y then
table.insert(downKeys, {x = x, y = y})
end
else
table.insert(downMarks, {active = true, x = x, y = y, time_remaining = DOWN_ANI_LENGTH})
end
else
for _, v in pairs(downMarks) do
if v.x == x and v.y == y then
v.active = false
break
end
end
for k, v in pairs(downKeys) do
if v.x == x and v.y == y then
table.remove(downKeys, k)
break
end
end
end
gridDirty = true
end
function grid_redraw()
local DOWN_BRIGHTNESS = 1
local TRAIL_BRIGHTNESS = 1
local OUTSIDE_BRIGHTNESS = 1
local INACTIVE_BRIGHTNESS = 2
local ACTIVE_BRIGHTNESS = 4
local brightness
-- Draw trails
for x = 1, 16 do
for y = 1, 16 do
if trails[x][y] then gridLEDs[x][y] = util.round(util.linlin(0, TRAIL_ANI_LENGTH, 0, TRAIL_BRIGHTNESS, trails[x][y]))
else gridLEDs[x][y] = 0 end
if (x > params:get("pattern_width") or y > params:get("pattern_height")) and gridLEDs[x][y] < OUTSIDE_BRIGHTNESS then gridLEDs[x][y] = OUTSIDE_BRIGHTNESS end
end
end
-- Draw down marks
for k, v in pairs(downMarks) do
brightness = util.round(util.linlin(0, DOWN_ANI_LENGTH, 0, DOWN_BRIGHTNESS, v.time_remaining))
for i = 1, grid_w do
if gridLEDs[i][v.y] < brightness then gridLEDs[i][v.y] = brightness end
end
for i = 1, grid_h do
if gridLEDs[v.x][i] < brightness then gridLEDs[v.x][i] = brightness end
end
if v.active and gridLEDs[v.x][v.y] < INACTIVE_BRIGHTNESS then gridLEDs[v.x][v.y] = INACTIVE_BRIGHTNESS end
end
-- Draw remove animations
for _, v in pairs(removeAnimations) 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 gridLEDs[i][v.position] < brightness then gridLEDs[i][v.position] = brightness end
end
else
for i = 1, grid_h do
if gridLEDs[v.position][i] < brightness then gridLEDs[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
gridLEDs[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
gridLEDs[tx][t.position] = brightness
end
end
end
end
for x = 1, grid_w do
for y = 1, grid_h do
gridDevice.led(x, y, gridLEDs[x][y])
end
end
gridDevice.refresh()
end
function redraw()
screen.clear()
--Scale name
screen.move(5, 10)
screen.level(15)
screen.text(MusicUtil.note_num_to_name(rootNote) .. " " .. tonality)
if string.find((MusicUtil.note_num_to_name(rootNote)),"%#") then
screen.move_rel(-5,0)
end
screen.move(99, 10)
screen.level(3)
screen.text('jump: ')
if shiftA then screen.level(15) else screen.level(3) end
screen.text(pivotAmount)
screen.move(100,17)
screen.level(3)
screen.text('octv: ')
screen.text(math.floor(gridScale[1]/12))
screen.move_rel(-29,7)
screen.move(128,60)
if shiftA then screen.level(15) else screen.level(3) end
screen.text_right(options.traffic_length_NAMES[params:get("traffic_length")])
local patternDisplay
if pat.rec == 1 then
patternDisplay = 'rec'
else
if progression.isplaying then patternDisplay = 'pattern'
else patternDisplay = nil end
end
if patternDisplay then
screen.move(128,53)
screen.level(3)
screen.text_right(patternDisplay)
else
screen.move(128,53)
screen.level (0)
end
-- Scale notes
local x, y = 5, 14
local scale_note_names = MusicUtil.note_nums_to_names(gridScale, false)
local COLS = 4
--print (#scale_note_names)
if #scale_note_names <17 then
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 scale_note_names[i] == nil then
break
elseif 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 is_active then screen.level(15)
else screen.level(3) end
screen.move(x, y)
screen.text(scale_note_names[i])
x = x + 18
end
end
screen.update()
end
function cleanup()
pat:stop()
pat = nil
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment