-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathextrovert-self-seqfuncs.lua
More file actions
185 lines (143 loc) · 6.49 KB
/
Copy pathextrovert-self-seqfuncs.lua
File metadata and controls
185 lines (143 loc) · 6.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
return {
-- Check whether a seq's incoming gate-value divides cleanly into the global tick
checkGateMatch = function(self, s)
-- If the seq has no incoming gate, that defaults to a match
if not self.seq[s].incoming.gate then
return true
end
-- If the global tick doesn't correspond to a sequence's incoming GATE size, then there is no match, so return false
local curgate = math.floor(self.gridx * (self.tick / self.longticks))
local longchunk = self.longticks / self.gridx
local tickmatch = (self.tick - 1) % longchunk
local gatematch = curgate % self.seq[s].incoming.gate
if (gatematch ~= 0) or (tickmatch ~= 0) then
return false
end
-- Return true for match, after filtering out non-matches
return true
end,
-- Cycle through all MIDI commands on the active tick within every active sequence
iterateAllSequences = function(self)
-- Increment global tick, bounded by global gate-size
self.tick = (self.tick % self.longticks) + 1
-- If the GATE button isn't held, and the current tick is the first tick in a new column, update the GATE counting buttons
if (not self.ctrlflags.gate)
and (((self.tick - 1) % (self.longticks / self.gridx)) == 0)
then
self:sendGateCountButtons()
end
-- Decay all currently-active sustains
self:decayAllSustains()
-- Send all regular commands within all sequences, and check against longseq
for i = 1, (self.gridy - 2) * self.gridx do
self:iterateSequence(i)
end
end,
-- Iterate through a sequence's incoming flags, increase its tick pointer under certain conditions, and send off all relevant notes
iterateSequence = function(self, s)
local ticks = #self.seq[s].tick
local chunk = ticks / self.gridx
-- Get the current column, or false if there is no active pointer, for later comparison
local oldcol = self.seq[s].pointer and math.ceil(math.max(1, self.seq[s].pointer - 1) / self.gridx)
-- If the sequence has no incoming gate, or it matches the global tick, parse its incoming command
if (not self.seq[s].incoming.gate) or self:checkGateMatch(s) then
-- Set the sequence's gate-flag to false, because it is either being fulfilled, or empty
self.seq[s].incoming.gate = false
-- If any commands are set...
if self.seq[s].incoming.cmd then
-- Get the sequence's incoming command
local c = table.remove(self.seq[s].incoming.cmd, 1)
local args = self.seq[s].incoming.cmd
local swapsend = false
if c == "parsePressSwap" then
swapsend = ((args[1] == s) and args[2]) or args[1]
end
-- Parse the sequence's incoming command
self[c](self, unpack(args))
-- Set the seq's incoming-command to false
self.seq[s].incoming.cmd = false
if swapsend then
self:updateSeqButton(swapsend)
self:sendMetaSeqRow(swapsend)
end
self:updateSeqButton(s) -- Update on-screen GUI
end
end
-- If, after the previous changes, the sequence still has an active pointer, then iterate a tick's worth of the sequence
if self.seq[s].pointer then
self:sendTickNotes(s, self.seq[s].pointer)
-- Advance the pointer by 1, and bound it within its loop-range
self.seq[s].pointer = self.seq[s].pointer + 1
local oldp = self.seq[s].pointer
local low = self.seq[s].loop.low or 1
local high = self.seq[s].loop.high or self.gridx
if self.seq[s].pointer > (chunk * high) then
self.seq[s].pointer = self.seq[s].pointer % (chunk * high)
self:buildScatterTable(s) -- Build a new SCATTER table on every loop
end
if self.seq[s].pointer < (chunk * (low - 1)) then
self.seq[s].pointer = (chunk * (low - 1)) + 1
end
end
-- If the seq's active column has shifted or been emptied, send an updated sequence row to the Monome apparatus
local newcol = self.seq[s].pointer and math.ceil(math.max(1, self.seq[s].pointer - 1) / self.gridx)
if not self.ctrlflags.pitch then
if newcol ~= oldcol then
self:sendMetaSeqRow(s)
end
end
end,
-- Build a meta version of a sequence's tick-table, based on its SCATTER values
buildScatterTable = function(self, s)
if #self.seq[s].sfactors == 0 then
self.seq[s].metaticks = {}
return nil
end
local ticks = #self.seq[s].tick
local tempnotes = {}
local shiftnotes = {}
for i = 1, #self.seq[s].tick do
self.seq[s].metatick[i] = {}
end
-- Copy all commands from the original sequence, and store extra info on NOTE commands
for k, v in pairs(self.seq[s].tick) do
for _, vv in pairs(v) do
if vv[2] == 144 then -- If this is a NOTE, increase NOTE-counter, and store its tempnotes-index
table.insert(tempnotes, {k, deepCopy(vv)})
else -- Else, for non-NOTE commands, put them straight into the meta-sequence
table.insert(self.seq[s].metatick[k], deepCopy(vv)) -- Copy every command into a tempnotes table
end
end
end
local limit = math.max(1, #tempnotes * (1 - self.seq[s].samount))
while #tempnotes > limit do
local pnote = table.remove(tempnotes, math.random(1, #tempnotes)) -- Get random NOTE command to shift
table.insert(shiftnotes, pnote) -- Put the note into the notes-to-be-shifted table
end
-- Put non-shifted tempnotes into the sequence's metatick table
for _, v in pairs(tempnotes) do
local tick, note = unpack(v)
table.insert(self.seq[s].metatick[tick], note)
end
-- Shift the positions of all remaining notes
for _, v in pairs(shiftnotes) do
local didput = false -- Track whether a note was successfully placed
local tick, note = unpack(v) -- Unpack the previously combined note elements
local fdup = deepCopy(self.seq[s].sfactors) -- Make a copy of seq[s].sfactors, to avoid depopulating the original
while #fdup > 0 do -- While there are viable distance-factors remaining...
local factor = table.remove(fdup, math.random(#fdup)) -- Get a random factor
local dist = self.tpq * factor -- Get a distance-value, of (TPQ * factor)
local newtick = (((tick + dist) - 1) % ticks) + 1 -- Get the note's new tick-position, wrapping to sequence boundaries
if #self.seq[s].metatick[newtick] == 0 then -- If the tick's metatick slot is empty...
table.insert(self.seq[s].metatick[newtick], note) -- Put the note in the metatick tick
didput = true -- Confirm that a note was placed
break -- Exit the while-loop
end
end
if not didput then -- If a note wasn't successfully placed, then place it in a random factor that overlaps with other note-starts
local newtick = (((tick + (self.tpq * self.seq[s].sfactors[math.random(#self.seq[s].sfactors)])) - 1) % ticks) + 1
table.insert(self.seq[s].metatick[newtick], note)
end
end
end,
}