Last active
January 15, 2019 04:31
-
-
Save ypxk/e1d204a7781589ee76d61154b860f253 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
-- 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