diff --git a/MIDI Editor/MIDI Ex Machina/RobU - MIDI Ex Machina.lua b/MIDI Editor/MIDI Ex Machina/RobU - MIDI Ex Machina.lua index 134e953..efe6188 100644 --- a/MIDI Editor/MIDI Ex Machina/RobU - MIDI Ex Machina.lua +++ b/MIDI Editor/MIDI Ex Machina/RobU - MIDI Ex Machina.lua @@ -7,17 +7,17 @@ #### Features note randomiser - selectable root note, octave, scale - - note probability sliders + - note probability per scale note - randomise all or selected notes - - octave doubler, with probability slider - - force first note to root note option - - permute selected notes + - octave doubler, with probability + - force first note to root of the scale option + - shuffle notes (fisher-yates) monophonic random sequence generator - - note length probability sliders + - note length probability - grid size control - - velocity accent level, and probabilty slider - - legato probability slider + - velocity accent level, and probabilty + - legato probability - various generation options euclidean sequence generator @@ -28,40 +28,44 @@ @donation https://www.paypal.me/RobUrquhart @link Reaper http://reaper.fm @link Forum Thread http://reaper.fm -@version 1.3.4 +@version 1.3.6 @author RobU @changelog - v1.3.5 - Fixed crash when item not selected in arrange view - Provided more meaningful warning messages (for not item, or no midi editor) + v1.3.6 + Added proper note shuffling using Fisher-Yates algorithm (polyphonic) + Removed the shitty 'Permute' scale hack :) + Fixed several corner-cases where notes might get eaten by the randomiser + Code clean-up in preparation for GUI update @provides [main=midi_editor] . [nomain] eGUI.lua [nomain] euclid.lua - [nomain] persistence.lua Reaper 5.x Extensions: SWS Licenced under the GPL v3 --]] + + + +-- requires ------------------------------------------------------------------ -------------------------------------------------------------------------------- --- REQUIRES --------------------------------------------------------------------------------- + package.path = debug.getinfo(1,"S").source:match[[^@?(.*[\/])[^\/]-$]] .."?.lua;".. package.path local e = require 'eGUI' local b = require 'euclid' -local p = require 'persistence' -- currently unused, i.e. no preset save, load, nada... --- ToDo preset save, load, etc... + + +-- globals ------------------------------------------------------------------- -------------------------------------------------------------------------------- --- GLOBAL VARIABLES START --------------------------------------------------------------------------------- + m = {} -- all ex machina data + -- user changeable defaults are marked with "(option)" -m.debug = false m.OS = reaper.GetOS() -- window -m.win_title = "RobU : MIDI Ex Machina - v1.3.5"; m.win_dockstate = 0 +m.win_title = "RobU : MIDI Ex Machina - v1.3.6"; m.win_dockstate = 0 m.win_x = 10; m.win_y = 10; m.win_w = 900; m.win_h = 280 -- window dimensions m.win_bg = {0, 0, 0} -- background colour m.def_zoom = 4 -- 100% (option) @@ -78,7 +82,7 @@ else end -- default octave & key --- due to some quirk, oct 4 is really oct 3... +-- due to reaper.ini offset value, oct 4 might be oct 3... (fixed in next build) m.oct = 4; m.key = 1; m.root = 0 -- (options, except m.root) -- midi editor, take, grid @@ -90,7 +94,8 @@ m.reaGrid = 0 m.rndAllNotesF = false -- all notes or only selected notes (option) m.rndOctX2F = false -- enable double scale randomisation (option) m.rndFirstNoteF = true; -- first note is always root (option) -m.rndPermuteF = false; m.pHash = 0 -- midi item state changes +--m.rndPermuteF = false; +m.pHash = 0 -- midi item state changes m.rndOctProb = 1 -- (option - min 0, max 10) -- sequence generator @@ -142,7 +147,6 @@ m.scales = { {0, 2, 4, 7, 9, 12, name = "Pentatonic Maj"}, {0, 3, 5, 7, 10, 12, name = "Pentatonic min"}, {0, 1, 4, 5, 6, 8, 11, 12, name = "Persian"}, - {name = "Permute"} } -- a list of scales available to the note randomiser, more can be added manually if required -- each value is the interval step from the root note of the scale (0) including the octave (12) @@ -161,305 +165,88 @@ pExtSaveStateF = false -- when true, update the pExtState for saving pExtLoadStateF = true -- when true, load the pExtState pExtState = {} -- Reaper project ext state table pExtStateStr = "" -- pickled string. a nom a nom a nom... --------------------------------------------------------------------------------- --- GLOBAL VARIABLES END --------------------------------------------------------------------------------- - --------------------------------------------------------------------------------- --- Utility Functions Start --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- -local function Wrap (n, max) --- Wrap(n, max) -return n wrapped between 'n' and 'max' - n = n % max - if (n < 1) then n = n + max end - return n -end - -local function RGB2Dec(r, g, b) --- RGB2Dec(r, g, b) - takes 8 bit r, g, b values, returns decimal (0 to 1) - if r < 0 or r > 255 then r = wrap(r, 255) end - if g < 0 or g > 255 then g = wrap(g, 255) end - if b < 0 or b > 255 then b = wrap(b, 255) end - return r/255, g/255, b/255 -end -local function RGB2Packed(r, g, b) --- RGB2Packed(r, g, b) - returns a packed rgb value - local floor = math.floor - g = (g << 8) - b = (b << 16) - return floor(r + g + b) -end -local function ConMsg(str) --- ConMsg(str) - outputs 'str' to the Reaper console - reaper.ShowConsoleMsg(str .."\n") -end --------------------------------------------------------------------------------- --- Pickle table serialization - Steve Dekorte, http://www.dekorte.com, Apr 2000 --------------------------------------------------------------------------------- -function pickle(t) - return Pickle:clone():pickle_(t) -end --------------------------------------------------------------------------------- -Pickle = { - clone = function (t) local nt = {} - for i, v in pairs(t) do - nt[i] = v - end - return nt -end -} --------------------------------------------------------------------------------- -function Pickle:pickle_(root) - if type(root) ~= "table" then - error("can only pickle tables, not " .. type(root) .. "s") - end - self._tableToRef = {} - self._refToTable = {} - local savecount = 0 - self:ref_(root) - local s = "" - while #self._refToTable > savecount do - savecount = savecount + 1 - local t = self._refToTable[savecount] - s = s .. "{\n" - for i, v in pairs(t) do - s = string.format("%s[%s]=%s,\n", s, self:value_(i), self:value_(v)) - end - s = s .. "},\n" - end - return string.format("{%s}", s) -end --------------------------------------------------------------------------------- -function Pickle:value_(v) - local vtype = type(v) - if vtype == "string" then return string.format("%q", v) - elseif vtype == "number" then return v - elseif vtype == "boolean" then return tostring(v) - elseif vtype == "table" then return "{"..self:ref_(v).."}" - else error("pickle a " .. type(v) .. " is not supported") - end -end --------------------------------------------------------------------------------- -function Pickle:ref_(t) - local ref = self._tableToRef[t] - if not ref then - if t == self then error("can't pickle the pickle class") end - table.insert(self._refToTable, t) - ref = #self._refToTable - self._tableToRef[t] = ref - end - return ref -end --------------------------------------------------------------------------------- --- unpickle --------------------------------------------------------------------------------- -function unpickle(s) - if type(s) ~= "string" then - error("can't unpickle a " .. type(s) .. ", only strings") - end - local gentables = load("return " .. s) - local tables = gentables() - for tnum = 1, #tables do - local t = tables[tnum] - local tcopy = {} - for i, v in pairs(t) do tcopy[i] = v end - for i, v in pairs(tcopy) do - local ni, nv - if type(i) == "table" then ni = tables[i[1]] else ni = i end - if type(v) == "table" then nv = tables[v[1]] else nv = v end - t[i] = nil - t[ni] = nv - end - end - return tables[1] -end --------------------------------------------------------------------------------- --- Utility Functions End +-- midi ex machina functions ------------------------------------------------- -------------------------------------------------------------------------------- +-- note buffers -------------------------------------------------------------- -------------------------------------------------------------------------------- --- Ex Machina Functions Start --------------------------------------------------------------------------------- -function ClearTable(t) --- ClearTable(t) - set all items in 2D table 't' to nil - local debug = false - if debug or m.debug then ConMsg("ClearTable()") end - for k, v in pairs(t) do - t[k] = nil - end -end -function CopyTable(t1, t2) --- CopyTable(t1, t2) - copies note data from t1 to t2 - ClearTable(t2) - local i = 1 - while t1[i] do - local j = 1 - t2[i] = {} - while (t1[i][j] ~= nil) do - t2[i][j] = t1[i][j] - j = j + 1 - end --while (t1[i][j] - i = i + 1 - end -- while t1[i] -end --------------------------------------------------------------------------------- --- Note Buffers --------------------------------------------------------------------------------- local function NewNoteBuf() -- NewNoteBuf() - add a new note buffer to the table, returns handle - local debug = false - if debug or m.debug then ConMsg("NewNoteBuf()") end + m.notebuf.i = m.notebuf.i + 1 m.notebuf.max = m.notebuf.max + 1 m.notebuf[m.notebuf.i] = {} - if debug or m.debug then - str = "created buffer\n" - str = str .. "buffer index = " .. tostring(m.notebuf.i) .. "\n" - ConMsg(str) - end + return m.notebuf[m.notebuf.i] end local function GetNoteBuf() -- GetNoteBuf() - returns handle to the current note buffer - local debug = false - if debug or m.debug then ConMsg("GetNoteBuf()") end + if m.notebuf.i >= 1 then - if debug or m.debug then - str = "retrieved buffer\n" - str = str .. "buffer index = " .. tostring(m.notebuf.i) .. "\n" - ConMsg(str) - end + return m.notebuf[m.notebuf.i] end end local function UndoNoteBuf() -- UndoNoteBuf() - points to previous note buffer - local debug = false - if debug or m.debug then ConMsg("UndoNoteBuf()") end + if m.notebuf.i > 1 then m.notebuf.i = m.notebuf.i -1 - if debug or m.debug then - str = "removed buffer " .. tostring(m.notebuf.i + 1) .. "\n" - str = str .. "buffer index = " .. tostring(m.notebuf.i) .. "\n" - ConMsg(str) - end - else - if debug or m.debug then - str = "nothing to undo...\n" - str = str .. "buffer index = " .. tostring(m.notebuf.i) .. "\n" - ConMsg(str) - end end end local function PurgeNoteBuf(idx) -- PurgeNoteBuf() - purge all note buffers from current+1 to end - local debug = false - if debug or m.debug then - ConMsg("PurgeNoteBuf()") - ConMsg("current idx = " .. tostring(m.notebuf.i)) - ConMsg("max idx = " .. tostring(m.notebuf.max)) - end + while m.notebuf.max > m.notebuf.i do m.notebuf[m.notebuf.max] = nil - if debug or m.debug then ConMsg("purging buffer " .. tostring(m.notebuf.max)) - end m.notebuf.max = m.notebuf.max - 1 end end --------------------------------------------------------------------------------- --- GetItemLength(t) - get length of take 't', set various global vars --- currently it only returns the item length (used in Sequencer and Euclid) --------------------------------------------------------------------------------- -function GetItemLength() - local debug = false - if debug or m.debug then ConMsg("GetItemLength()") end - - --mItem = reaper.GetSelectedMediaItem(0, 0) - mItemLen = reaper.GetMediaItemInfo_Value(m.mItem, "D_LENGTH") - mBPM, mBPI = reaper.GetProjectTimeSignature2(0) - msPerMin = 60000 - msPerQN = msPerMin / mBPM - numQNPerItem = (mItemLen * 1000) / msPerQN - numBarsPerItem = numQNPerItem / 4 - ItemPPQN = numQNPerItem * m.ppqn - if debug or m.debug then - ConMsg("ItemLen (ms) = " .. mItemLen) - ConMsg("mBPM = " .. mBPM) - ConMsg("MS Per QN = " .. msPerQN) - ConMsg("Num of QN = " .. numQNPerItem) - ConMsg("Num of Bar = " .. numBarsPerItem) - ConMsg("Item size ppqn = " .. ItemPPQN .. "\n") - end - mItemTake = reaper.GetTake(m.mItem, 0) -- should fix looped items - ItemPPQN = reaper.BR_GetMidiSourceLenPPQ(mItemTake) -- thanks Thrash! - return ItemPPQN -end -function GetReaperGrid(gridRad) --- GetReaperGrid() - get the current grid size, set global var m.reaGrid - local debug = false - if debug or m.debug then ConMsg("GetReaperGrid()") end - if m.activeTake then - m.reaGrid, __, __ = reaper.MIDI_GetGrid(m.activeTake) -- returns quarter notes - if gridRad then -- if a grid object was passed, update it - if m.reaGrid == 0.25 then gridRad.val1 = 1 -- 1/16 - elseif m.reaGrid == 0.5 then gridRad.val1 = 2 -- 1/8 - elseif m.reaGrid == 1 then gridRad.val1 = 3 -- 1/4 - end -- m.reaGrid - end - else - if debug or m.debug then ConMsg("No Active Take\n") end - end -- m.activeTake -end +-- delete, get, insert ------------------------------------------------------- -------------------------------------------------------------------------------- --- GetPermuteScaleFromTake(t) - fill a scale buffer 't' from the active take --------------------------------------------------------------------------------- -function GetPermuteScaleFromTake(t) - local debug = false - if debug or m.debug then ConMsg("GetPermuteScaleFromTake()") end - local i, j = 1, 0 + +function DeleteNotes() +-- DeleteNotes() - delete all notes from the active take + + local i, num_notes = 0, 0 + if m.activeTake then - local __, num_notes, num_cc, num_sysex = reaper.MIDI_CountEvts(m.activeTake) - if num_notes > 0 then - ClearTable(t) - for i = 1, num_notes do - __, selected, __, __, __, __, pitch, __ = reaper.MIDI_GetNote(m.activeTake, i-1) - if selected == true then - j = j + 1 - t[j] = pitch - m.root - end - end -- for i - end --if num_notes - else - if debug or m.debug then ConMsg("No Active Take") end - end -- m.activeTake + __, num_notes, __, __ = reaper.MIDI_CountEvts(m.activeTake) + + for i = 0, num_notes do + reaper.MIDI_DeleteNote(m.activeTake, 0) + end --for + end --m.activeTake end --------------------------------------------------------------------------------- --- GetNotesFromTake() - fill a note buffer from the active take --------------------------------------------------------------------------------- + function GetNotesFromTake() - local debug = false - if debug or m.debug then ConMsg("GetNotesFromTake()") end +-- GetNotesFromTake() - fill a note buffer from the active take + local i, t + if m.activeTake then + reaper.MIDI_Sort(m.activeTake) local _retval, num_notes, num_cc, num_sysex = reaper.MIDI_CountEvts(m.activeTake) + if num_notes > 0 then t = GetNoteBuf(); if t == nil then t = NewNoteBuf() end ClearTable(t) + for i = 1, num_notes do _retval, selected, muted, startppq, endppq, channel, pitch, velocity = reaper.MIDI_GetNote(m.activeTake, i-1) t[i] = {} + t[i][0] = i t[i][1] = selected t[i][2] = muted t[i][3] = startppq @@ -470,162 +257,252 @@ function GetNotesFromTake() t[i][8] = velocity end -- for i end -- num_notes - PrintNotes(GetNoteBuf()) - else -- no active take - if debug or m.debug then ConMsg("No Active Take") end end -- m.activeTake end --------------------------------------------------------------------------------- --- DeleteNotes() - delete all notes from the active take --------------------------------------------------------------------------------- -function DeleteNotes() - local debug = false - if debug or m.debug then ConMsg("DeleteNotes()") end - local i, num_notes = 0, 0 - if m.activeTake then - __, num_notes, __, __ = reaper.MIDI_CountEvts(m.activeTake) - for i = 0, num_notes do - reaper.MIDI_DeleteNote(m.activeTake, 0) - end --for - else - if debug or m.debug then ConMsg("No Active Take") end - end --m.activeTake -end --------------------------------------------------------------------------------- --- SetRootNote(octave, key) - returns new root midi note --------------------------------------------------------------------------------- -function SetRootNote(octave, key) - local debug = false - local o = octave * 12 - local k = key - 1 - if debug or m.debug then ConMsg("SetRootNote() - Note = " .. tostring(o + k)) end - return o + k -end --------------------------------------------------------------------------------- --- GenProbTable(preProbTable, slidersTable, probTable) --- creates an event probability table using values from sliders --------------------------------------------------------------------------------- -function GenProbTable(preProbTable, sliderTable, probTable) - local debug = false - if debug or m.debug then ConMsg("GenProbTable()") end - local i, j, k, l = 1, 1, 1, 1 - local floor = math.floor - ClearTable(probTable) - for i, v in ipairs(preProbTable) do - if sliderTable[j].val1 > 0 then - for l = 1, (sliderTable[j].val1) do - probTable[k] = preProbTable[i] - k = k + 1 - end -- for l - end -- if sliderTable[j] - j = j + 1 - end -end --------------------------------------------------------------------------------- --- GenAccentTable(probTable, velSlider, probSlider) --- creates an event probability table using values from sliders --------------------------------------------------------------------------------- -function GenAccentTable(probTable, velSlider, probSlider) - local debug = false - if debug or m.debug then ConMsg("GenAccentTable()") end - local i, j = 1, 1 - ClearTable(probTable) - -- insert normal velocity - for i = 1, (probSlider.max - probSlider.val1) do - probTable[j] = math.floor(velSlider.val1) - j = j + 1 - end - -- insert the accented velocity - for i = 1, (probSlider.val1) do - probTable[j] = math.floor(velSlider.val2) - j = j + 1 - end -end --------------------------------------------------------------------------------- --- GenLegatoTable(probTable, velSlider, probSlider) --- creates an event probability table using values from sliders --------------------------------------------------------------------------------- -function GenLegatoTable(probTable, probSlider) - local debug = false - if debug or m.debug then ConMsg("GenLegatoTable()") end - local i, j = 1, 1 - ClearTable(probTable) - -- no legato - for i = 1, (probSlider.max - probSlider.val1) do - probTable[j] = m.legato - j = j + 1 - end - -- legato - for i = 1, (probSlider.val1) do - probTable[j] = 0 - j = j + 1 - end + +function InsertNotes() +-- InsertNotes(note_buffer) - insert notes in the active take + + DeleteNotes() + local i = 1 + + if m.activeTake and m.mItem then + local noteLength = 0 + local gridSize = m.reaGrid * m.ppqn + local itemLength = GetItemLength() + local noteShift = m.seqShift * gridSize + local t1 = GetNoteBuf() + local t2 = {} -- for note shifting + CopyTable(t1, t2) + + for k, v in pairs(t2) do -- do note shifting + noteLength = v[4] - v[3] + v[3] = v[3] + noteShift + v[4] = v[4] + noteShift + + if v[3] >= itemLength then -- positive shift + v[3] = v[3] - itemLength + v[4] = v[4] - itemLength + + elseif v[3] < 0 then -- negative shift + v[3] = v[3] + itemLength + v[4] = v[4] + itemLength + + end + + if v[4] > itemLength then v[4] = itemLength - 1 end + + end + + + while t2[i] do + reaper.MIDI_InsertNote(m.activeTake, t2[i][1], t2[i][2], t2[i][3], t2[i][4], t2[i][6], t2[i][7], t2[i][8], false) + --1=selected, 2=muted, 3=startppq, 4=endppq, 5=len, 6=chan, 7=pitch, 8=vel, noSort) + i = i + 1 + end -- while t2[i] + + reaper.MIDI_Sort(m.activeTake) + reaper.MIDIEditor_OnCommand(m.activeEditor, 40435) -- all notes off + + end -- m.activeTake end + + + +-- randomiser ---------------------------------------------------------------- -------------------------------------------------------------------------------- + +function GenOctaveTable(probTable, probSlider) -- GenOctaveTable(probTable, velSlider, probSlider) -- creates an event probability table using values from sliders --------------------------------------------------------------------------------- -function GenOctaveTable(probTable, probSlider) - local debug = false - if debug or m.debug then ConMsg("GenOctaveTable()") end + local i, j = 1, 1 ClearTable(probTable) + -- single octave for i = 1, (probSlider.max - probSlider.val1) do probTable[j] = 0 j = j + 1 end + -- double octave for i = 1, (probSlider.val1) do probTable[j] = 12 j = j + 1 end end --------------------------------------------------------------------------------- + +function GetUniqueNote(note_in) +-- GetUniqueNote - returns a random note number based on selected scale, and various flags + local rand_note + + rand_note = (note_in[0]==1 and m.rndFirstNoteF) and -- select on note pos 1 && first note is root flag + m.root or m.root + m.noteProbTable[math.random(1, #m.noteProbTable)] -- get appropriate note (fixed/random) + + if m.rndOctX2F then rand_note = rand_note + m.octProbTable[math.random(1, #m.octProbTable)] end -- get octave + + return rand_note + + --old code for posterity :P + --t_sel[s][7] = (t_sel[s][0]==1 and m.rndFirstNoteF) and -- select on note pos 1 && first note is root flag + -- m.root or m.root + m.noteProbTable[math.random(1, #m.noteProbTable)] -- get appropriate note (fixed/random) + --if m.rndOctX2F then t_sel[s][7] = t_sel[s][7] + m.octProbTable[math.random(1, #m.octProbTable)] end -- get octave +end + +function RandomiseNotes() +-- RandomiseNotes - randomise all/selected notes + + local t1, t_all, t_sel = GetNoteBuf(), NewNoteBuf(), {} + local a, s, t_all_len, t_sel_len = 0, 0, 0, 0 + local unique, retry = true, 0 + local overs = {} + + CopyNoteTable(t1, t_all) + CopyNoteTable(t_all, t_sel, true) + + if #t_sel < 1 then return else t_sel_len = #t_sel end + + for s = 1, t_sel_len do -- for each note in t_sel + unique = true -- assume unique + + t_sel[s][7] = GetUniqueNote(t_sel[s]) + + for a = 1, #t_all do -- for all notes, find the overlaps between the selected notes and or notes + + if t_sel[s][0] ~= t_all[a][0] then -- exclude the same note ID + + if (t_sel[s][3] >= t_all[a][3] and t_sel[s][3] < t_all[a][4]) or -- sel start pos overlap with all? + (t_all[a][3] >= t_sel[s][3] and t_all[a][3] < t_sel[s][4]) then -- all start pos overlap with sel? + table.insert(overs, t_all[a]) -- update pos overlaps + + if t_sel[s][7] == t_all[a][7] then -- note overlap + unique = false -- note is not unique + end + end + end + end -- for a = 1, #t_all do + + while not unique do + unique = true -- assume the next random note will be unique + t_sel[s][7] = GetUniqueNote(t_sel[s]) + + for _, over in ipairs(overs) do -- check all overlapping notes for duplicates + if t_sel[s][7] == over[7] then unique = false end -- not unique + end + + retry = retry + 1; if retry > 10 then break end -- break loop if we can't find a solution + end -- while not unique + + if retry > 11 then return 1 else retry = 1 end -- return if we can't find a solution + + overs={} + end -- for s = 1, t_sel_len + + -- write back to the note buffer + for s = 1, #t_sel do + t_all[t_sel[s][0]][7] = t_sel[s][7] + end + + PurgeNoteBuf() + InsertNotes() + if m.activeTake then + __, pHash = reaper.MIDI_GetHash(m.activeTake, false, 0) + m.pHash = pHash + end +end + +function SetRootNote(octave, key) +-- SetRootNote(octave, key) - returns new root midi note + + local o = octave * 12 + local k = key - 1 + + return o + k +end + +function SetScale(scaleName, allScales, scale) -- SetScale() -- copies a scale from allScales to scale, key = scaleName --------------------------------------------------------------------------------- -function SetScale(scaleName, allScales, scale) - local debug = false - if debug or m.debug then ConMsg("SetScale()") end + ClearTable(scale) + for i = 1, #allScales, 1 do if scaleName == allScales[i].name then - if scaleName == "Permute" then - m.rndPermuteF = true - GetPermuteScaleFromTake(scale) - else - m.rndPermuteF = false - end for k, v in pairs(allScales[i]) do scale[k] = v end break end end - if debug or m.debug then ConMsg("scaleName = " .. tostring(scaleName)) end + end --------------------------------------------------------------------------------- --- SetSeqGridSizes() --------------------------------------------------------------------------------- -function SetSeqGridSizes(sliderTable) - local debug = false - if debug or m.debug then ConMsg("SetSeqGridSizes()") end - for k, v in pairs(sliderTable) do - if sliderTable[k].label == "1/16" then m.preSeqProbTable[k] = 0.25 - elseif sliderTable[k].label == "1/8" then m.preSeqProbTable[k] = 0.5 - elseif sliderTable[k].label == "1/4" then m.preSeqProbTable[k] = 1.0 - elseif sliderTable[k].label == "Rest" then m.preSeqProbTable[k] = -1.0 - end + +function ShuffleNotes() +-- ShuffleNotes - shuffle all/selected notes using Fisher-Yates algo + + local t1, t_all, t_sel = GetNoteBuf(), NewNoteBuf(), {} + local a, s, t, t_all_len, t_sel_len = 0, 0, 0, 0, 0 + local unique, retry = true, 0 + local overs = {} + + CopyNoteTable(t1, t_all) -- copy the note buffer to an empty stack note buffer + CopyNoteTable(t_all, t_sel, true) -- copy selected notes only to a temp empty table + + if #t_sel < 2 then return else t_sel_len = #t_sel end + + for s = 1, t_sel_len do -- for each note in t_sel + unique = true -- assume unique + + t = math.random(s, t_sel_len) + t_sel[s][7], t_sel[t][7] = t_sel[t][7], t_sel[s][7] -- shuffle one note + + for a = 1, #t_all do -- for all notes, find the overlaps between the selected notes and or notes + + if t_sel[s][0] ~= t_all[a][0] then -- exclude the same note ID + + if (t_sel[s][3] >= t_all[a][3] and t_sel[s][3] < t_all[a][4]) or -- sel start pos overlap with all? + (t_all[a][3] >= t_sel[s][3] and t_all[a][3] < t_sel[s][4]) then -- all start pos overlap with sel? + table.insert(overs, t_all[a]) -- update pos overlaps + + if t_sel[s][7] == t_all[a][7] then -- note overlap + unique = false -- note is not unique + end + end + end + end -- for a = 1, #t_all do + + while not unique do + unique = true -- assume the next shuffled note will be unique + t = math.random(s, t_sel_len) + t_sel[s][7], t_sel[t][7] = t_sel[t][7], t_sel[s][7] -- shuffle one note + + for _, over in ipairs(overs) do -- check all overlapping notes for duplicates + if t_sel[s][7] == over[7] then unique = false end -- not unique + end + + retry = retry + 1; if retry > 10 then break end -- break loop if we can't find a solution + end -- while not unique + + if retry > 11 then return 1 else retry = 1 end -- return if we can't find a solution + + overs={} + + end -- for i = 1, t_sel_len do + + -- write back to the note buffer + for i = 1, #t_sel do + t_all[t_sel[i][0]][7] = t_sel[i][7] end + + PurgeNoteBuf() + InsertNotes() + end --------------------------------------------------------------------------------- + +function UpdateSliderLabels(sliderTable, preProbTable) -- UpdateSliderLabels() args t_noteSliders, m.preNoteProbTable -- sets the sliders to the appropriate scale notes, including blanks --------------------------------------------------------------------------------- -function UpdateSliderLabels(sliderTable, preProbTable) - local debug = false - if debug or m.debug then ConMsg("UpdateSliderLabels()") end + for k, v in pairs(sliderTable) do if preProbTable[k] then -- if there's a Scale note -- set the slider to the note name @@ -637,91 +514,27 @@ function UpdateSliderLabels(sliderTable, preProbTable) end end end + + + +-- sequencer ----------------------------------------------------------------- -------------------------------------------------------------------------------- --- GetUniqueNote() --------------------------------------------------------------------------------- -function GetUniqueNote(tNotes, noteIdx, noteProbTable, octProbTable) - local debug = false - if debug and m.debug then ConMsg("GetUniqueNote()") end - newNote = m.root + noteProbTable[math.random(1, #noteProbTable)] - if m.rndOctX2F and not m.rndPermuteF then - newNote = newNote + octProbTable[math.random(1, #octProbTable)] - end - if #m.dupes == 0 then -- dupe table is empty - m.dupes.i = 1; m.dupes[m.dupes.i] = {} -- add note to the dupe table - m.dupes[m.dupes.i].srtpos = tNotes[noteIdx][3] - m.dupes[m.dupes.i].endpos = tNotes[noteIdx][4] - m.dupes[m.dupes.i].midi = newNote - return newNote - elseif tNotes[noteIdx][3] >= m.dupes[m.dupes.i].srtpos - and tNotes[noteIdx][3] < m.dupes[m.dupes.i].endpos then -- note overlaps with previous note - m.dupes.i = m.dupes.i + 1; m.dupes[m.dupes.i] = {} -- add note to dupe table - m.dupes[m.dupes.i].srtpos = tNotes[noteIdx][3] - m.dupes[m.dupes.i].endpos = tNotes[noteIdx][4] - unique = false - while not unique do - newNote = m.root + noteProbTable[math.random(1,#noteProbTable)] - if m.rndOctX2F and not m.rndPermuteF then - newNote = newNote + octProbTable[math.random(1, #octProbTable)] - end - unique = true - for i = 1, m.dupes.i - 1 do -- check all previous overlapping notes - if m.dupes[i].midi == newNote then unique = false end -- clash, try again - end -- m.dupes.i - end -- not unique - m.dupes[m.dupes.i].midi = newNote -- update dupe table - return newNote - else -- note does not overlap with previous note - m.dupes = {}; m.dupes.i = 1; m.dupes[m.dupes.i] = {} -- reset dupe table - m.dupes[m.dupes.i].srtpos = tNotes[noteIdx][3] - m.dupes[m.dupes.i].endpos = tNotes[noteIdx][4] - m.dupes[m.dupes.i].midi = newNote - return newNote - end -- if #m.dupes -end --------------------------------------------------------------------------------- --- RandomiseNotesPoly(noteProbTable) --------------------------------------------------------------------------------- -function RandomiseNotesPoly(noteProbTable) - local debug = false - if debug or m.debug then ConMsg("RandomiseNotesPoly()") end - m.dupes.i = 1 - local i = 1 - local t1, t2 = GetNoteBuf(), NewNoteBuf() - CopyTable(t1, t2) - while t2[i] do - if t2[i][1] == true or m.rndAllNotesF then -- if selected, or all notes flag is true - if i == 1 and m.rndFirstNoteF then -- if selected, the first not is always root of scale - t2[i][7] = m.root - else - t2[i][7] = GetUniqueNote(t1, i, noteProbTable, m.octProbTable) - end - end - i = i + 1 - end -- while t1[i] - PurgeNoteBuf() - InsertNotes() - if m.rndPermuteF and m.activeTake then - __, pHash = reaper.MIDI_GetHash(m.activeTake, false, 0) - m.pHash = pHash - end -end --------------------------------------------------------------------------------- --- GenSequence(seqProbTable, accProbTable, accSlider, legProbTable) --------------------------------------------------------------------------------- + function GenSequence(seqProbTable, accProbTable, accSlider, legProbTable) - local debug = false - if debug or m.debug then ConMsg("GenSequence()") end +-- GenSequence(seqProbTable, accProbTable, accSlider, legProbTable) + local t, t2 = NewNoteBuf(), GetNoteBuf() CopyTable(t2, t) GetReaperGrid() -- populates m.reaGrid ClearTable(t) + local itemPos = 0 local gridSize = m.reaGrid * m.ppqn local itemLength = GetItemLength() local noteStart, noteEnd, noteLen, noteVel = 0, 0, 0, 0 local newNote = 0 local noteCount = 0; restCount = 0 + while itemPos < itemLength do if m.seqFirstNoteF and noteCount == 0 then -- handle first note flag @@ -736,6 +549,7 @@ function GenSequence(seqProbTable, accProbTable, accSlider, legProbTable) if newNote == -1 then itemPos = itemPos + gridSize restCount = restCount + 1 + else noteStart = itemPos noteLen = newNote * m.ppqn @@ -765,83 +579,70 @@ function GenSequence(seqProbTable, accProbTable, accSlider, legProbTable) t[noteCount][8] = noteVel -- velocity end -- newNote end -- itemPos < itemLength - if debug and m.debug then PrintNotes(t) end + PurgeNoteBuf() InsertNotes() end --------------------------------------------------------------------------------- --- GenBjorklund(pulses, steps, rotation, accProbTable, accSlider) --------------------------------------------------------------------------------- -function GenBjorklund(pulses, steps, rotation, accProbTable, accSlider) - local debug = false - if debug or m.debug then ConMsg("GenBjorklund()") end - local floor = math.floor - local t, t2 = NewNoteBuf(), GetNoteBuf() - CopyTable(t2, t) - GetReaperGrid() -- populates m.reaGrid - --t = GetNoteBuf(); if t == nil then t = NewNoteBuf() end -- pre-undo - ClearTable(t) - local itemPos = 0 - local gridSize = m.reaGrid * m.ppqn - local itemLength = GetItemLength() - local noteStart, noteEnd, noteLen, noteVel = 0, 0, 0, 0 - local newNote = 0 - local noteCount = 0; restCount = 0 - local pulse = floor(pulses.val1 + 0.5) - local step = floor(steps.val1 + 0.5) - local pattern = b.generate(pulse, step) - local rot = floor(rotation.val1 + 0.5) - local idx = (-rot) + 1; idx = Wrap(idx, step) - while itemPos < itemLength do - if pattern[idx] then - noteStart = itemPos - noteLen = gridSize - noteEnd = noteStart + noteLen - itemPos = itemPos + noteLen - if m.eucAccentF then -- handle accent flag - noteVel = accProbTable[math.random(1, #accProbTable)] - else - noteVel = floor(accSlider.val1) - end -- m.seqAccentF - --noteVel = accProbTable[math.random(1, #accProbTable)] - noteCount = noteCount + 1 - t[noteCount] = {} - t[noteCount][1] = true -- selected - t[noteCount][2] = false -- muted - t[noteCount][3] = noteStart -- startppqn - t[noteCount][4] = noteEnd + m.legato -- endppqn - t[noteCount][5] = noteLen -- note length - t[noteCount][6] = 0 -- channel - t[noteCount][7] = m.root -- note number - t[noteCount][8] = noteVel -- velocity - else - itemPos = itemPos + gridSize - restCount = restCount + 1 - end - idx = idx + 1 - idx = Wrap(idx, step) - end - PurgeNoteBuf() - InsertNotes() -end --------------------------------------------------------------------------------- --- GenNoteAttributes(accF, accProb, accVal, legF, legVal) -- accent, legato only --------------------------------------------------------------------------------- + +function GenAccentTable(probTable, velSlider, probSlider) +-- GenAccentTable(probTable, velSlider, probSlider) +-- creates an event probability table using values from sliders + + local i, j = 1, 1 + ClearTable(probTable) + + -- insert normal velocity + for i = 1, (probSlider.max - probSlider.val1) do + probTable[j] = math.floor(velSlider.val1) + j = j + 1 + end + + -- insert the accented velocity + for i = 1, (probSlider.val1) do + probTable[j] = math.floor(velSlider.val2) + j = j + 1 + end +end + +function GenLegatoTable(probTable, probSlider) +-- GenLegatoTable(probTable, velSlider, probSlider) +-- creates an event probability table using values from sliders + + local i, j = 1, 1 + ClearTable(probTable) + + -- no legato + for i = 1, (probSlider.max - probSlider.val1) do + probTable[j] = m.legato + j = j + 1 + end + + -- legato + for i = 1, (probSlider.val1) do + probTable[j] = 0 + j = j + 1 + end +end + function GenNoteAttributes(accF, accProbTable, accSlider, legF, legProbTable) - local debug = false - if debug or m.debug then ConMsg("GenNoteAttributes()") end +-- GenNoteAttributes(accF, accProb, accVal, legF, legVal) -- accent, legato only + if not accF and not legF then return end local t1, t2 = GetNoteBuf(), NewNoteBuf() local i = 1 local noteStart, noteEnd, noteLen = 0, 0, 0 CopyTable(t1, t2) - if debug and m.debug then PrintNotes(t2) end + while t2[i] do + if t2[i][1] then + if accF then -- handle accent flag (8 = velocity) t2[i][8] = accProbTable[math.random(1, #accProbTable)] end -- end accent + if legF ~= 1 then -- no legato when called by euclid + if legF then -- handle legato flag (3 = noteStart, 4 = noteEnd, 5 = noteLen) noteLen = t2[i][5] if noteLen >= 960 + m.legato and noteLen <= 960 - m.legato then noteLen = 960 -- 1/4 @@ -854,128 +655,403 @@ function GenNoteAttributes(accF, accProbTable, accSlider, legF, legProbTable) end --selected i = i + 1 end -- while t1[i] - if debug and m.debug then PrintNotes(t2) end + PurgeNoteBuf() InsertNotes() end --------------------------------------------------------------------------------- --- InsertNotes(note_buffer) - insert notes in the active take --------------------------------------------------------------------------------- -function InsertNotes() - local debug = false - if debug or m.debug then ConMsg("\nInsertNotes()") end - DeleteNotes() - local i = 1 - if m.activeTake and m.mItem then - local noteLength = 0 - local gridSize = m.reaGrid * m.ppqn - local itemLength = GetItemLength() - local noteShift = m.seqShift * gridSize - local t1 = GetNoteBuf() - local t2 = {} -- for note shifting - CopyTable(t1, t2) - if debug then ConMsg("itemPPQN = "..itemLength) end - if debug then ConMsg("noteshift = "..noteShift) end +function GetItemLength() +-- GetItemLength(t) - get length of take 't', set various global vars +-- currently it only returns the item length (used in Sequencer and Euclid) + + --mItem = reaper.GetSelectedMediaItem(0, 0) + mItemLen = reaper.GetMediaItemInfo_Value(m.mItem, "D_LENGTH") + mBPM, mBPI = reaper.GetProjectTimeSignature2(0) + msPerMin = 60000 + msPerQN = msPerMin / mBPM + numQNPerItem = (mItemLen * 1000) / msPerQN + numBarsPerItem = numQNPerItem / 4 + ItemPPQN = numQNPerItem * m.ppqn + mItemTake = reaper.GetTake(m.mItem, 0) -- should fix looped items + ItemPPQN = reaper.BR_GetMidiSourceLenPPQ(mItemTake) -- thanks Thrash! + return ItemPPQN + +end + +function GetReaperGrid(gridRad) +-- GetReaperGrid() - get the current grid size, set global var m.reaGrid + + if m.activeTake then + m.reaGrid, __, __ = reaper.MIDI_GetGrid(m.activeTake) -- returns quarter notes - for k, v in pairs(t2) do -- do note shifting - noteLength = v[4] - v[3] - v[3] = v[3] + noteShift - v[4] = v[4] + noteShift - - if v[3] >= itemLength then -- positive shift - v[3] = v[3] - itemLength - v[4] = v[4] - itemLength - - elseif v[3] < 0 then -- negative shift - v[3] = v[3] + itemLength - v[4] = v[4] + itemLength + if gridRad then -- if a grid object was passed, update it + if m.reaGrid == 0.25 then gridRad.val1 = 1 -- 1/16 + elseif m.reaGrid == 0.5 then gridRad.val1 = 2 -- 1/8 + elseif m.reaGrid == 1 then gridRad.val1 = 3 -- 1/4 + end -- m.reaGrid + end + end -- m.activeTake +end - end +function GenProbTable(preProbTable, sliderTable, probTable) +-- GenProbTable(preProbTable, slidersTable, probTable) +-- creates an event probability table using values from sliders + + local i, j, k, l = 1, 1, 1, 1 + local floor = math.floor + ClearTable(probTable) + + for i, v in ipairs(preProbTable) do + if sliderTable[j].val1 > 0 then + for l = 1, (sliderTable[j].val1) do + probTable[k] = preProbTable[i] + k = k + 1 + end -- for l + end -- if sliderTable[j] + j = j + 1 + end +end + +function SetSeqGridSizes(sliderTable) +-- SetSeqGridSizes() + for k, v in pairs(sliderTable) do + if sliderTable[k].label == "1/16" then m.preSeqProbTable[k] = 0.25 + elseif sliderTable[k].label == "1/8" then m.preSeqProbTable[k] = 0.5 + elseif sliderTable[k].label == "1/4" then m.preSeqProbTable[k] = 1.0 + elseif sliderTable[k].label == "Rest" then m.preSeqProbTable[k] = -1.0 + end + end +end + + + +-- euclidiser ---------------------------------------------------------------- +-------------------------------------------------------------------------------- +function GenBjorklund(pulses, steps, rotation, accProbTable, accSlider) +-- GenBjorklund(pulses, steps, rotation, accProbTable, accSlider) + + local floor = math.floor + local t, t2 = NewNoteBuf(), GetNoteBuf() + CopyTable(t2, t) + GetReaperGrid() -- populates m.reaGrid + ClearTable(t) + + local itemPos = 0 + local gridSize = m.reaGrid * m.ppqn + local itemLength = GetItemLength() + local noteStart, noteEnd, noteLen, noteVel = 0, 0, 0, 0 + local newNote = 0 + local noteCount = 0; restCount = 0 + local pulse = floor(pulses.val1 + 0.5) + local step = floor(steps.val1 + 0.5) + local pattern = b.generate(pulse, step) + local rot = floor(rotation.val1 + 0.5) + local idx = (-rot) + 1; idx = Wrap(idx, step) + + while itemPos < itemLength do + + if pattern[idx] then + noteStart = itemPos + noteLen = gridSize + noteEnd = noteStart + noteLen + itemPos = itemPos + noteLen - if v[4] > itemLength then v[4] = itemLength - 1 end + if m.eucAccentF then -- handle accent flag + noteVel = accProbTable[math.random(1, #accProbTable)] + else + noteVel = floor(accSlider.val1) + end -- m.seqAccentF - if debug then ConMsg("v3 = "..v[3].." ".."v4 = "..v[4].." ".."notelen = "..noteLength) end + --noteVel = accProbTable[math.random(1, #accProbTable)] + noteCount = noteCount + 1 + t[noteCount] = {} + t[noteCount][1] = true -- selected + t[noteCount][2] = false -- muted + t[noteCount][3] = noteStart -- startppqn + t[noteCount][4] = noteEnd + m.legato -- endppqn + t[noteCount][5] = noteLen -- note length + t[noteCount][6] = 0 -- channel + t[noteCount][7] = m.root -- note number + t[noteCount][8] = noteVel -- velocity + + else + itemPos = itemPos + gridSize + restCount = restCount + 1 end + idx = idx + 1 + idx = Wrap(idx, step) + end + + PurgeNoteBuf() + InsertNotes() +end + + +-- utility functions ---------------------------------------------------------- +-------------------------------------------------------------------------------- + +function ClearTable(t) +-- ClearTable(t) - set all items in 2D table 't' to nil + + for k, v in pairs(t) do + t[k] = nil + end +end + +function CopyTable(t1, t2) -- deprecated +-- CopyTable(t1, t2) - copies note data from t1 to t2 + ClearTable(t2) -- safety, not needed if new table supplied + + local i, j = 1, 1 + + while t1[i] do + j = 1 + t2[i] = {} - while t2[i] do - reaper.MIDI_InsertNote(m.activeTake, t2[i][1], t2[i][2], t2[i][3], t2[i][4], t2[i][6], t2[i][7], t2[i][8], false) - --1=selected, 2=muted, 3=startppq, 4=endppq, 5=len, 6=chan, 7=pitch, 8=vel, noSort) - i = i + 1 - end -- while t2[i] - reaper.MIDI_Sort(m.activeTake) - reaper.MIDIEditor_OnCommand(m.activeEditor, 40435) -- all notes off - else - if debug or m.debug then ConMsg("No Active Take") end - end -- m.activeTake + while (t1[i][j] ~= nil) do + t2[i][j] = t1[i][j] + j = j + 1 + end --while (t1[i][j] + + i = i + 1 + end -- while t1[i] end --------------------------------------------------------------------------------- --- PrintNotes - arg note_buffer t; print note_buffer to reaper console --------------------------------------------------------------------------------- +function CopyNoteTable(t1, t2, selected) +-- Copies notes from table1 to table2, optionally filtered on selected notes + + ClearTable(t2) + + local i, j = 1, 1 + + if not selected then-- copy all + while t1[i] do + table.insert(t2, t1[i]) + t2[i].idx = i + i = i + 1 + end + + else -- copy selected + while t1[i] do + if t1[i][1] or m.rndAllNotesF then + table.insert(t2, t1[i]) + t2[#t2].idx = i + end + i = i + 1 + end + end + +end + +function PrintNote(t) -- debug code +-- PrintNote - arg note t; print a single note to reaper console + + if not t then return end + + local j = 0 + local str = "id \t sel \t mut \t s_ppq \t e_ppq \t leng \t chan \t pitch \t vel \n" + + while t[j] ~= nil do + str = str .. tostring(t[j]) .. "\t" + j = j + 1 + end + + ConMsg(str) +end + function PrintNotes(t) -- debug code - local debug = false - if debug or m.debug then ConMsg("PrintNotes()") end - if not t then return end - local i = 1 - local str = "sel \t mut \t s_ppq \t e_ppq \t leng \t chan \t pitch \t vel \n" +-- PrintNotes - arg note_buffer t; print note_buffer to reaper console + + if not t then return end + + local i, j = 1, 0 + local str = "id \t sel \t mut \t s_ppq \t e_ppq \t leng \t chan \t pitch \t vel \n" + while t[i] do - j = 1 + j = 0 + while (t[i][j] ~= nil) do str = str .. tostring(t[i][j]) .. "\t" j = j + 1 end + str = str .. "\n" i = i + 1 end -- while t[i] + str = str .. "\n" - if debug or m.debug then ConMsg(str) end + ConMsg(str) end --------------------------------------------------------------------------------- + +function PrintTable(t) -- debug code (deprecate?) -- PrintTable - print table to reaper console --------------------------------------------------------------------------------- -function PrintTable(t) -- debug code - local debug = false - if debug or m.debug then ConMsg("PrintTable()") end + local str = "" for k, v in pairs(t) do str = str .. tostring(v) .. "\t" end str = str .. "\n" - if debug or m.debug then ConMsg(str) end + end --------------------------------------------------------------------------------- --- ShowMessage(textbox, message number) - display or hide a message for user --------------------------------------------------------------------------------- + + +function Wrap (n, max) +-- Wrap(n, max) -return n wrapped between 'n' and 'max' + + n = n % max + if (n < 1) then n = n + max end + + return n +end + +function RGB2Dec(r, g, b) +-- RGB2Dec(r, g, b) - takes 8 bit r, g, b values, returns decimal (0 to 1) + + if r < 0 or r > 255 then r = wrap(r, 255) end + if g < 0 or g > 255 then g = wrap(g, 255) end + if b < 0 or b > 255 then b = wrap(b, 255) end + + return r/255, g/255, b/255 +end + +function RGB2Packed(r, g, b) +-- RGB2Packed(r, g, b) - returns a packed rgb value + + local floor = math.floor + g = (g << 8) + b = (b << 16) + + return floor(r + g + b) +end + +function ConMsg(str) +-- ConMsg(str) - outputs 'str' to the Reaper console + reaper.ShowConsoleMsg(str .."\n") +end + function ShowMessage(tb, msgNum) - local debug = false - if debug and m.debug then ConMsg("ShowMessage() ") end +-- ShowMessage(textbox, message number) - display or hide a message for user + if msgNum == 0 then tb.tab = (1 << 9) tb.label = "" + elseif msgNum == 1 then tb.tab = 0 tb.label = "MIDI Editor Closed" + elseif msgNum == 2 then tb.tab = 0 tb.label = "Please select a MIDI Item in the Arrange Window" end - e.gScaleState = true + + e.gScaleState = true +end + + +-- table serialization - steve dekorte ---------------------------------------- +-------------------------------------------------------------------------------- +function pickle(t) + return Pickle:clone():pickle_(t) +end + +Pickle = { + clone = function (t) local nt = {} + for i, v in pairs(t) do + nt[i] = v + end + return nt +end +} + +function Pickle:pickle_(root) + + if type(root) ~= "table" then + error("can only pickle tables, not " .. type(root) .. "s") + end + + self._tableToRef = {} + self._refToTable = {} + local savecount = 0 + self:ref_(root) + local s = "" + + while #self._refToTable > savecount do + savecount = savecount + 1 + local t = self._refToTable[savecount] + s = s .. "{\n" + + for i, v in pairs(t) do + s = string.format("%s[%s]=%s,\n", s, self:value_(i), self:value_(v)) + end + + s = s .. "},\n" + end + + return string.format("{%s}", s) +end + +function Pickle:value_(v) + local vtype = type(v) + + if vtype == "string" then return string.format("%q", v) + elseif vtype == "number" then return v + elseif vtype == "boolean" then return tostring(v) + elseif vtype == "table" then return "{"..self:ref_(v).."}" + else error("pickle a " .. type(v) .. " is not supported") + end +end + +function Pickle:ref_(t) + + local ref = self._tableToRef[t] + + if not ref then + if t == self then error("can't pickle the pickle class") end + table.insert(self._refToTable, t) + ref = #self._refToTable + self._tableToRef[t] = ref + end + + return ref end --------------------------------------------------------------------------------- --- FUNCTIONS END --------------------------------------------------------------------------------- +function unpickle(s) + + if type(s) ~= "string" then + error("can't unpickle a " .. type(s) .. ", only strings") + end + + local gentables = load("return " .. s) + local tables = gentables() + + for tnum = 1, #tables do + local t = tables[tnum] + local tcopy = {} + for i, v in pairs(t) do tcopy[i] = v end + + for i, v in pairs(tcopy) do + local ni, nv + if type(i) == "table" then ni = tables[i[1]] else ni = i end + if type(v) == "table" then nv = tables[v[1]] else nv = v end + t[i] = nil + t[ni] = nv + end + end + + return tables[1] +end + + + + +-- GUI Elements -------------------------------------------------------------- -------------------------------------------------------------------------------- --- GUI START --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- --- GUI Elements --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- --- Main Window + +-- Main Window --------------------------------------------------------------- -------------------------------------------------------------------------------- + -- Persistent window elements local winFrame = e.Frame:new({0}, 5, 5, m.win_w - 10, m.win_h - 10, e.col_grey4) local zoomDrop = e.Droplist:new({0}, 5, 5, 40, 22, e.col_green, "", e.Arial, m.font_sz, e.col_grey8, 4, {"70%", "80%", "90%", "100%", "110%", "120%", "140%", "160%", "180%", "200%"}) @@ -986,24 +1062,35 @@ local layerBtn03 = e.Button:new({0}, 205, m.win_h - 25, 100, 20, e.col_grey5, "E local layerBtn04 = e.Button:new({0}, 305, m.win_h - 25, 100, 20, e.col_grey5, "Options", e.Arial, m.font_sz, e.col_grey7) local undoBtn = e.Button:new({0}, m.win_w-85, m.win_h -25, 40, 20, e.col_grey5, "Undo", e.Arial, m.font_sz, e.col_grey7) local redoBtn = e.Button:new({0}, m.win_w-45, m.win_h -25, 40, 20, e.col_grey5, "Redo", e.Arial, m.font_sz, e.col_grey7) + -- Persistent window element table t_winElements = {winFrame, zoomDrop, winText, layerBtn01, layerBtn02, layerBtn03, layerBtn04, undoBtn, redoBtn} + + +-- Common Elements ----------------------------------------------------------- -------------------------------------------------------------------------------- --- Common Elements --------------------------------------------------------------------------------- + -- key, octave, & scale droplists dx, dy, dw, dh = 25, 70, 110, 20 -local keyDrop = e.Droplist:new({1, 2, 3}, dx, dy, dw, dh, e.col_blue, "Root Note", e.Arial, m.font_sz, e.col_grey8, m.key, m.notes) -local octDrop = e.Droplist:new({1, 2, 3}, dx, dy + 45, dw, dh, e.col_blue, "Octave ", e.Arial, m.font_sz, e.col_grey8, m.oct,{0, 1, 2, 3, 4, 5, 6, 7}) -local scaleDrop = e.Droplist:new({1, 2, 3}, dx, dy + 90, dw, dh, e.col_blue, "Scale", e.Arial, m.font_sz, e.col_grey8, 1, m.scalelist) +local keyDrop = e.Droplist:new({1, 2, 3}, dx, dy, (dw*0.5)-5, dh, e.col_blue, "Key", e.Arial, m.font_sz, e.col_grey8, m.key, m.notes) +local octDrop = e.Droplist:new({1, 2, 3}, dx+(dw*0.5)+5, dy, (dw*0.5)-5, dh, e.col_blue, "Oct ", e.Arial, m.font_sz, e.col_grey8, m.oct,{0, 1, 2, 3, 4, 5, 6, 7}) +local scaleDrop = e.Droplist:new({1, 2, 3}, dx, dy + 50, dw, dh, e.col_blue, "Scale", e.Arial, m.font_sz, e.col_grey8, 1, m.scalelist) local t_Droplists = {keyDrop, octDrop, scaleDrop} + + + +-- Randomiser Layer ---------------------------------------------------------- -------------------------------------------------------------------------------- --- Randomiser Layer --------------------------------------------------------------------------------- + +-- note shuffle button +local shuffleBtn = e.Button:new({1}, 25, 165, 110, 25, e.col_green, "Shuffle", e.Arial, m.font_sz, e.col_grey8) + -- note randomise button -local randomBtn = e.Button:new({1}, 25, 205, 110, 25, e.col_green, "Generate", e.Arial, m.font_sz, e.col_grey8) +local randomBtn = e.Button:new({1}, 25, 205, 110, 25, e.col_green, "Randomise", e.Arial, m.font_sz, e.col_grey8) + + -- note weight sliders local nx, ny, nw, nh, np = 160, 50, 30, 150, 40 local noteSldr01 = e.Vert_Slider:new({1}, nx, ny, nw, nh, e.col_blue, "", e.Arial, m.font_sz, e.col_grey8, 1, 0, 0, 12, 1) @@ -1019,34 +1106,45 @@ local noteSldr10 = e.Vert_Slider:new({1}, nx+(np*9), ny, nw, nh, e.col_blue, "", local noteSldr11 = e.Vert_Slider:new({1}, nx+(np*10), ny, nw, nh, e.col_blue, "", e.Arial, m.font_sz, e.col_grey8, 1, 0, 0, 12, 1) local noteSldr12 = e.Vert_Slider:new({1}, nx+(np*11), ny, nw, nh, e.col_blue, "", e.Arial, m.font_sz, e.col_grey8, 1, 0, 0, 12, 1) local noteSldr13 = e.Vert_Slider:new({1}, nx+(np*12), ny, nw, nh, e.col_blue, "", e.Arial, m.font_sz, e.col_grey8, 1, 0, 0, 12, 1) + -- Note probability slider table local t_noteSliders = {noteSldr01, noteSldr02, noteSldr03, noteSldr04, noteSldr05, noteSldr06, noteSldr07, noteSldr08, noteSldr09, noteSldr10, noteSldr11, noteSldr12, noteSldr13} + -- Note probability slider label (Textbox) - right-click to reset all local probSldrText = e.Textbox:new({1}, nx, 210, 510, 20, e.col_grey5, "Note Weight Sliders", e.Arial, m.font_sz, e.col_grey7) + -- Note octave doubler probability slider local octProbSldr = e.Vert_Slider:new({1}, nx+(np*13) + 10, ny, nw, nh, e.col_blue, "%", e.Arial, m.font_sz, e.col_grey8, m.rndOctProb, 0, 0, 10, 1) local octProbText = e.Textbox:new({1}, nx+(np*13) + 10, 210, (nw), 20, e.col_grey5, "Oct", e.Arial, m.font_sz, e.col_grey7) + -- Note randomiser options local noteOptionsCb = e.Checkbox:new({1}, nx+(np*14)+10, ny+30, 30, 30, e.col_orange, "", e.Arial, m.font_sz, e.col_grey8, {0,0,0}, {"All / Sel Notes", "1st Note = Root", "Octave X2"}) local noteOptionText = e.Textbox:new({1}, nx+(np*14)+20, 210, (nw*4), 20, e.col_grey5, "Options", e.Arial, m.font_sz, e.col_grey7) + + + +-- Sequencer Layer ----------------------------------------------------------- -------------------------------------------------------------------------------- --- Sequencer Layer --------------------------------------------------------------------------------- + -- sequence generate button local sequenceBtn = e.Button:new({2}, 25, 205, 110, 25, e.col_yellow, "Generate", e.Arial, m.font_sz, e.col_grey8) local sx, sy, sw, sh, sp = 160, 50, 30, 150, 40 + -- sequencer grid size radio selector local seqGridRad = e.Rad_Button:new({2,3}, sx, sy + 40, 30, 30, e.col_yellow, "", e.Arial, m.font_sz, e.col_grey8, 1, {"1/16", "1/8", "1/4"}) local seqGridText = e.Textbox:new({2,3}, sx, 210, (sw*2)+20, 20, e.col_grey5, "Grid Size", e.Arial, m.font_sz, e.col_grey7) + -- sequence grid probability sliders local seqSldr16 = e.Vert_Slider:new({2}, sx+(sp*3), sy, sw, sh, e.col_blue, "1/16", e.Arial, m.font_sz, e.col_grey8, 0, 0, 0, 16, 1) local seqSldr8 = e.Vert_Slider:new({2}, sx+(sp*4), sy, sw, sh, e.col_blue, "1/8", e.Arial, m.font_sz, e.col_grey8, 0, 0, 0, 16, 1) local seqSldr4 = e.Vert_Slider:new({2}, sx+(sp*5), sy, sw, sh, e.col_blue, "1/4", e.Arial, m.font_sz, e.col_grey8, 0, 0, 0, 16, 1) local seqSldrRest = e.Vert_Slider:new({2}, sx+(sp*6), sy, sw, sh, e.col_blue, "Rest", e.Arial, m.font_sz, e.col_grey8, 0, 0, 0, 16, 1) + -- sequence grid probability slider table local t_seqSliders = {seqSldr16, seqSldr8, seqSldr4, seqSldrRest} + -- sequence grid probability sliders label - right click to reset all (per grid size selection) local seqSldrText = e.Textbox:new({2}, sx + (sp * 3), 210, (sw * 5), 20, e.col_grey5, "Size Weight Sliders", e.Arial, m.font_sz, e.col_grey7) @@ -1068,52 +1166,58 @@ local seqShiftText = e.Textbox:new({2}, sx + (sp * 11) + 10, 210, sw * 3, 20, e. -- Sequencer options local seqOptionsCb = e.Checkbox:new({2}, sx+(np * 14) + 10, sy + 5, 30, 30, e.col_orange, "", e.Arial, m.font_sz, e.col_grey8, {0,0,0,0,0}, {"Generate", "1st Note Always", "Accent", "Legato", "Rnd Notes"}) + + + +-- Euclidean Layer ----------------------------------------------------------- -------------------------------------------------------------------------------- --- Euclid Layer --------------------------------------------------------------------------------- --- euclid generate button + +-- euclidean generate button local euclidBtn = e.Button:new({3}, 25, 205, 110, 25, e.col_orange, "Generate", Arial, m.font_sz, e.col_grey8) + -- euclidean sliders local ex, ey, ew, eh, ep = 160, 50, 30, 150, 40 local euclidPulsesSldr = e.Vert_Slider:new({3}, ex+(ep*3), ey, ew, eh, e.col_blue, "Puls", Arial, m.font_sz, e.col_grey8, m.eucPulses, 0, 1, 24, 1) local euclidStepsSldr = e.Vert_Slider:new({3}, ex+(ep*4), ey, ew, eh, e.col_blue, "Step", Arial, m.font_sz, e.col_grey8, m.eucSteps, 0, 1, 24, 1) local euclidRotationSldr = e.Vert_Slider:new({3}, ex+(ep*5), ey, ew, eh, e.col_blue, "Rot", Arial, m.font_sz, e.col_grey8, m.eucRot, 0, 0, 24, 1) local t_euclidSliders = {euclidPulsesSldr, euclidStepsSldr, euclidRotationSldr} --- euclid slider label - right click to reset all + +-- euclidean slider label - right click to reset all local txtEuclidLabel = e.Textbox:new({3}, ex + (ep * 3), 210, (ew * 3) + 20, 20, e.col_grey5, "Euclid Sliders", Arial, m.font_sz, e.col_grey7) --- Sequencer options + +-- euclidean options local eucOptionsCb = e.Checkbox:new({3}, ex + (ep * 14) + 10, ey + 40, 30, 30, e.col_orange, "", e.Arial, m.font_sz, e.col_grey8, {0,0,0}, {"Generate", "Accent", "Rnd Notes"}) --------------------------------------------------------------------------------- --- Options Layer + + +-- Options Layer ------------------------------------------------------------- -------------------------------------------------------------------------------- local optText = e.Textbox:new({4}, m.win_x + 10, m.win_y + 30, m.win_w - 40, m.win_h - 80, e.col_grey5, "Nothing to see here, yet...", e.Arial, m.font_sz, e.col_grey8) --------------------------------------------------------------------------------- --- Messages Layer + + +-- Messages Layer ------------------------------------------------------------ -------------------------------------------------------------------------------- local msgText = e.Textbox:new({9}, m.win_x + 10, m.win_y + 30, m.win_w - 40, m.win_h - 80, e.col_greym, "", e.Arial, 22, e.col_grey9) + + +-- Shared Element Tables ----------------------------------------------------- -------------------------------------------------------------------------------- --- Shared Element Tables --------------------------------------------------------------------------------- -local t_Buttons = {randomBtn, sequenceBtn, seqShiftLBtn, seqShiftRBtn, euclidBtn} +local t_Buttons = {randomBtn, shuffleBtn, sequenceBtn, seqShiftLBtn, seqShiftRBtn, euclidBtn} local t_Checkboxes = {noteOptionsCb, seqOptionsCb, eucOptionsCb} local t_RadButtons = {seqGridRad} local t_RSliders = {octProbSldr, seqAccRSldr, seqAccProbSldr, seqLegProbSldr} local t_Textboxes = {probSldrText, octProbText, seqGridText, seqSldrText, seqShiftVal, seqShiftText, seqAccSldrText, seqLegSldrText, txtEuclidLabel, optText, msgText} --------------------------------------------------------------------------------- + + + +-- main window gui functions ------------------------------------------------- -------------------------------------------------------------------------------- --- GUI Functions START --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- --- Main window --------------------------------------------------------------------------------- --- Window zoom droplist -zoomDrop.onLClick = function() -- window scaling - local debug = false - if debug or m.debug then ConMsg("\nzoomDrop.onLClick()") end + +zoomDrop.onLClick = function() -- Window zoom droplist - window scaling + if zoomDrop.val1 == 1 then e.gScale = 0.7 elseif zoomDrop.val1 == 2 then e.gScale = 0.8 elseif zoomDrop.val1 == 3 then e.gScale = 0.9 @@ -1125,7 +1229,7 @@ zoomDrop.onLClick = function() -- window scaling elseif zoomDrop.val1 == 9 then e.gScale = 1.8 elseif zoomDrop.val1 == 10 then e.gScale = 2.0 end - if debug or m.debug then ConMsg("zoom = " .. tostring(e.gScale)) end + -- Save state, close and reopen GFX window if not pExtState.win_x then __, m.win_x, m.win_y, __, __ = gfx.dock(-1,0,0,0,0) @@ -1134,124 +1238,139 @@ zoomDrop.onLClick = function() -- window scaling pExtState.zoomDrop = zoomDrop.val1 pExtSaveStateF = true end + m.zoomF = true end --- Layer 1 button -layerBtn01.onLClick = function() -- randomiser - local debug = false - if debug or m.debug then ConMsg("\nlayerBtn01.onLClick() (note randomiser)") end + +layerBtn01.onLClick = function() -- Layer 1 button - randomiser + e.gActiveLayer = 1 zoomDrop.r, zoomDrop.g, zoomDrop.b, zoomDrop.a = table.unpack(e.col_green) winText.r, winText.g, winText.b, winText.a = table.unpack(e.col_green) + layerBtn01.font_rgba = e.col_grey8 -- highlight layer 1 layerBtn01.r, layerBtn01.g, layerBtn01.b, layerBtn01.a = table.unpack(e.col_green) + layerBtn02.font_rgba = e.col_grey7 layerBtn02.r, layerBtn02.g, layerBtn02.b, layerBtn02.a = table.unpack(e.col_grey5) + layerBtn03.font_rgba = e.col_grey7 layerBtn03.r, layerBtn03.g, layerBtn03.b, layerBtn03.a = table.unpack(e.col_grey5) + layerBtn04.font_rgba = e.col_grey7 layerBtn04.r, layerBtn04.g, layerBtn04.b, layerBtn04.a = table.unpack(e.col_grey5) + e.gScaleState = true + -- set project ext state pExtState.activeLayer = e.gActiveLayer pExtSaveStateF = true end --- Layer 2 button -layerBtn02.onLClick = function() -- sequencer - local debug = false - if debug or m.debug then ConMsg("\nlayerBtn02.onLClick() (sequencer)") end + +layerBtn02.onLClick = function() -- Layer 2 button - sequencer + e.gActiveLayer = 2 zoomDrop.r, zoomDrop.g, zoomDrop.b, zoomDrop.a = table.unpack(e.col_yellow) winText.r, winText.g, winText.b, winText.a = table.unpack(e.col_yellow) + layerBtn01.font_rgba = e.col_grey7 layerBtn01.r, layerBtn01.g, layerBtn01.b, layerBtn01.a = table.unpack(e.col_grey5) + layerBtn02.font_rgba = e.col_grey8 -- highlight layer 2 layerBtn02.r, layerBtn02.g, layerBtn02.b, layerBtn02.a = table.unpack(e.col_yellow) + layerBtn03.font_rgba = e.col_grey7 layerBtn03.r, layerBtn03.g, layerBtn03.b, layerBtn03.a = table.unpack(e.col_grey5) + layerBtn04.font_rgba = e.col_grey7 layerBtn04.r, layerBtn04.g, layerBtn04.b, layerBtn04.a = table.unpack(e.col_grey5) + e.gScaleState = true - -- set project ext state + + -- set project ext state pExtState.activeLayer = e.gActiveLayer pExtSaveStateF = true end --- Layer 3 button -layerBtn03.onLClick = function() -- euclidean - local debug = false - if debug or m.debug then ConMsg("\nlayerBtn03.onLClick() (euclid)") end + +layerBtn03.onLClick = function() -- Layer 3 button - euclidean + e.gActiveLayer = 3 zoomDrop.r, zoomDrop.g, zoomDrop.b, zoomDrop.a = table.unpack(e.col_orange) winText.r, winText.g, winText.b, winText.a = table.unpack(e.col_orange) + layerBtn01.font_rgba = e.col_grey7 layerBtn01.r, layerBtn01.g, layerBtn01.b, layerBtn01.a = table.unpack(e.col_grey5) + layerBtn02.font_rgba = e.col_grey7 layerBtn02.r, layerBtn02.g, layerBtn02.b, layerBtn02.a = table.unpack(e.col_grey5) + layerBtn03.font_rgba = e.col_grey8 -- highlight layer 3 layerBtn03.r, layerBtn03.g, layerBtn03.b, layerBtn03.a = table.unpack(e.col_orange) + layerBtn04.font_rgba = e.col_grey7 layerBtn04.r, layerBtn04.g, layerBtn04.b, layerBtn04.a = table.unpack(e.col_grey5) + e.gScaleState = true - -- set project ext state + + -- set project ext state pExtState.activeLayer = e.gActiveLayer pExtSaveStateF = true end --- Layer 4 button -layerBtn04.onLClick = function() -- options - local debug = false - if debug or m.debug then ConMsg("\nlayerBtn04.onLClick() (options)") end + +layerBtn04.onLClick = function() -- Layer 4 button - options + e.gActiveLayer = 4 zoomDrop.r, zoomDrop.g, zoomDrop.b, zoomDrop.a = table.unpack(e.col_grey5) winText.r, winText.g, winText.b, winText.a = table.unpack(e.col_grey5) + layerBtn01.font_rgba = e.col_grey7 layerBtn01.r, layerBtn01.g, layerBtn01.b, layerBtn01.a = table.unpack(e.col_grey5) + layerBtn02.font_rgba = e.col_grey7 layerBtn02.r, layerBtn02.g, layerBtn02.b, layerBtn02.a = table.unpack(e.col_grey5) + layerBtn03.font_rgba = e.col_grey7 layerBtn03.r, layerBtn03.g, layerBtn03.b, layerBtn03.a = table.unpack(e.col_grey5) + layerBtn04.font_rgba = e.col_grey8 -- highlight layer 4 layerBtn04.r, layerBtn04.g, layerBtn04.b, layerBtn04.a = table.unpack(e.col_grey6) + e.gScaleState = true - -- set project ext state + + -- set project ext state pExtState.activeLayer = e.gActiveLayer pExtSaveStateF = true end --- Undo button -undoBtn.onLClick = function() -- undo - local debug = false - if debug or m.debug then ConMsg("\nundoBtn.onLClick()") end + +undoBtn.onLClick = function() -- Undo button UndoNoteBuf() InsertNotes() - PrintNotes(m.notebuf[m.notebuf.i]) end --- Redo button -redoBtn.onLClick = function() -- redo - local debug = false - if debug or m.debug then ConMsg("\nredoBtn.onLClick()") end + +redoBtn.onLClick = function() -- Redo button + if m.notebuf[m.notebuf.i + 1] ~= nil then - --PrintNotes(m.notebuf[m.notebuf.i + 1]) m.notebuf.i = m.notebuf.i + 1 InsertNotes() - --PrintNotes(m.notebuf[m.notebuf.i]) - else - if debug or m.debug then ConMsg("\nnothing to redo...") end end end --- Set default window options -function SetDefaultWindowOpts() - local debug = false - if debug or m.debug then ConMsg("SetDefaultWinOpts()") end + +-- defaults +function SetDefaultWindowOpts() -- Set default window options + if pExtState.zoomDrop then zoomDrop.val1 = pExtState.zoomDrop end + if pExtState.win_x or pExtState.win_y then m.win_x = pExtState.win_x m.win_y = pExtState.win_y end zoomDrop.onLClick() end --- Set default layer -function SetDefaultLayer() + +function SetDefaultLayer() -- Set default layer + if pExtState.activeLayer then if pExtState.activeLayer == 1 then layerBtn01.onLClick() elseif pExtState.activeLayer == 2 then layerBtn02.onLClick() @@ -1261,13 +1380,13 @@ function SetDefaultLayer() end end + + +-- randomiser gui functions -------------------------------------------------- -------------------------------------------------------------------------------- --- Note Randomiser --------------------------------------------------------------------------------- --- Randomiser button -randomBtn.onLClick = function() - local debug = false - if debug or m.debug then ConMsg("\nrandomBtn.onLClick()") end + +randomBtn.onLClick = function() -- Random button action + if m.activeTake and m.mItem then m.seqShift = 0; m.seqShiftMin = 0; m.seqShiftMax = 0 -- reset shift seqShiftVal.label = tostring(m.seqShift) @@ -1275,89 +1394,140 @@ randomBtn.onLClick = function() if #m.noteProbTable == 0 then return end GenOctaveTable(m.octProbTable, octProbSldr) GetNotesFromTake() - RandomiseNotesPoly(m.noteProbTable) + RandomiseNotes() + -- set project ext state pExtState.noteSliders = {} + for k, v in pairs(t_noteSliders) do pExtState.noteSliders[k] = v.val1 end + pExtState.rndOctProb = octProbSldr.val1 pExtSaveStateF = true end --m.activeTake end --- Randomiser options toggle logic -noteOptionsCb.onLClick = function() - local debug = false - if debug or m.debug then ConMsg("\nnoteOptionsCb.onLClick()") end + +shuffleBtn.onLClick = function() -- Shuffle button action + + if m.activeTake and m.mItem then + m.seqShift = 0; m.seqShiftMin = 0; m.seqShiftMax = 0 -- reset shift + seqShiftVal.label = tostring(m.seqShift) + GetNotesFromTake() + ShuffleNotes() + end + +end + +noteOptionsCb.onLClick = function() -- Randomiser options toggle logic + m.rndAllNotesF = noteOptionsCb.val1[1] == 1 and true or false -- All / Sel Notes m.rndFirstNoteF = noteOptionsCb.val1[2] == 1 and true or false -- 1st Note Root m.rndOctX2F = noteOptionsCb.val1[3] == 1 and true or false -- Octave X2 + pExtState.noteOptionsCb = {m.rndAllNotesF, m.rndFirstNoteF, m.rndOctX2F} pExtSaveStateF = true - if debug or m.debug then PrintTable(noteOptionsCb.val1) end + end --- Root Key droplist -keyDrop.onLClick = function() - local debug = false - if debug or m.debug then ConMsg("\nkeyDrop.onLClick()") end + +keyDrop.onLClick = function() -- Root Key droplist + m.key = keyDrop.val1 m.root = SetRootNote(m.oct, m.key) UpdateSliderLabels(t_noteSliders, m.preNoteProbTable) + -- set project ext state pExtState.key = m.key pExtState.root = m.root pExtSaveStateF = true end --- Octave droplist -octDrop.onLClick = function() - local debug = false - if debug or m.debug then ConMsg("\noctDrop.onLClick()") end + +octDrop.onLClick = function() -- Octave droplist + m.oct = octDrop.val1 m.root = SetRootNote(m.oct, m.key) + -- set project ext state pExtState.oct = m.oct pExtState.root = m.root pExtSaveStateF = true end --- Scale droplist -scaleDrop.onLClick = function() - local debug = false - if debug or m.debug then ConMsg("\nscaleDrop.onLClick()") end + +scaleDrop.onLClick = function() -- Scale droplist + SetScale(scaleDrop.val2[scaleDrop.val1], m.scales, m.preNoteProbTable) - if m.rndPermuteF then - noteOptionsCb.val1[1] = 0 - m.rndAllNotesF = false - end UpdateSliderLabels(t_noteSliders, m.preNoteProbTable) + -- set project ext state pExtState.curScaleName = scaleDrop.val2[scaleDrop.val1] pExtSaveStateF = true end --- Set default scale options -function SetDefaultScaleOpts() - local debug = false - if debug or m.debug then ConMsg("SetDefaultScaleOpts()") end +probSldrText.onRClick = function() -- Reset note probability sliders + + gfx.x = gfx.mouse_x; gfx.y = gfx.mouse_y + local result = gfx.showmenu("Reset Note Sliders") + + if result == 1 then + + for k, v in pairs(t_noteSliders) do -- reset the sliders + if v.label ~= "" then v.val1 = 1 end + end -- in pairs(t_noteSliders) + + if pExtState.noteSliders then -- write the new proj ext state + for k, v in pairs(t_noteSliders) do + if v.label ~= "" then pExtState.noteSliders[k] = v.val1 end + end -- in pairs(t_noteSliders) + end -- pExtState.noteSliders + + pExtSaveStateF = true -- set the ext state save flag + end -- result +end + +octProbText.onRClick = function() -- Reset octave probability slider + + gfx.x = gfx.mouse_x; gfx.y = gfx.mouse_y + local result = gfx.showmenu("Reset Octave Slider") + + if result == 1 then + octProbSldr.val1 = m.rndOctProb + + if pExtState.rndOctProb then -- write the new proj ext state + pExtState.rndOctProb = nil + end -- pExtState.noteSliders + + pExtSaveStateF = true -- set the ext state save flag + end -- result +end + +-- defaults +function SetDefaultScaleOpts() -- Set default scale options + -- if key saved in project state, load it if pExtState.key then m.key = math.floor(tonumber(pExtState.key)) keyDrop.val1 = m.key end + -- if octave saved in project state, load it if pExtState.oct then m.oct = math.floor(tonumber(pExtState.oct)) octDrop.val1 = m.oct end + -- set the midi note number for scale root m.root = SetRootNote(m.oct, m.key) + -- create a scale name lookup table for the gui (scaleDrop) for k, v in pairs(m.scales) do m.scalelist[k] = m.scales[k]["name"] end + -- if scale name saved in project state, load it if pExtState.curScaleName then m.curScaleName = pExtState.curScaleName end + -- update the scale dropbox val1 to match the scale table index for k, v in pairs(m.scales) do if v.name == m.curScaleName then scaleDrop.val1 = k end @@ -1366,81 +1536,53 @@ function SetDefaultScaleOpts() SetScale(m.curScaleName, m.scales, m.preNoteProbTable) --set chosen scale UpdateSliderLabels(t_noteSliders, m.preNoteProbTable) -- set sliders labels to current scale notes end --- Set default randomiser options -function SetDefaultRndOptions() - local debug = false - if debug or m.debug then ConMsg("SetDefaultRndOptions()") end + +function SetDefaultRndOptions() -- Set default randomiser options + -- if randomiser options were saved to project state, load them if pExtState.noteOptionsCb then m.rndAllNotesF = pExtState.noteOptionsCb[1] == true and true or false m.rndFirstNoteF = pExtState.noteOptionsCb[2] == true and true or false m.rndOctX2F = pExtState.noteOptionsCb[3] == true and true or false - end + end + -- set randomiser options using defaults, or loaded project state noteOptionsCb.val1[1] = (true and m.rndAllNotesF) and 1 or 0 -- all notes noteOptionsCb.val1[2] = (true and m.rndFirstNoteF) and 1 or 0 -- first note root noteOptionsCb.val1[3] = (true and m.rndOctX2F) and 1 or 0 -- octave doubler end --- Set default randomiser sliders -function SetDefaultRndSliders() - local debug = false - if debug or m.debug then ConMsg("SetDefaultRndSliders()") end - -- if randomiser sliders were saved to project state, load them - if pExtState.noteSliders then - for k, v in pairs(t_noteSliders) do - v.val1 = pExtState.noteSliders[k] - end - else - for k, v in pairs(t_noteSliders) do - v.val1 = 1 - end - end -- load note sliders pExtState - if pExtState.rndOctProb then -- octave probability slider - octProbSldr.val1 = pExtState.rndOctProb - else - octProbSldr.val1 = m.rndOctProb - end -end - --- Reset note probability sliders -probSldrText.onRClick = function() - gfx.x = gfx.mouse_x; gfx.y = gfx.mouse_y - local result = gfx.showmenu("Reset Note Sliders") - if result == 1 then - for k, v in pairs(t_noteSliders) do -- reset the sliders - if v.label ~= "" then v.val1 = 1 end - end -- in pairs(t_noteSliders) - if pExtState.noteSliders then -- write the new proj ext state - for k, v in pairs(t_noteSliders) do - if v.label ~= "" then pExtState.noteSliders[k] = v.val1 end - end -- in pairs(t_noteSliders) - end -- pExtState.noteSliders - pExtSaveStateF = true -- set the ext state save flag - end -- result -end --- Reset octave probability slider -octProbText.onRClick = function() - gfx.x = gfx.mouse_x; gfx.y = gfx.mouse_y - local result = gfx.showmenu("Reset Octave Slider") - if result == 1 then + +function SetDefaultRndSliders() -- Set default randomiser sliders + + -- if randomiser sliders were saved to project state, load them + if pExtState.noteSliders then + for k, v in pairs(t_noteSliders) do + v.val1 = pExtState.noteSliders[k] + end + else + for k, v in pairs(t_noteSliders) do + v.val1 = 1 + end + end -- load note sliders pExtState + + if pExtState.rndOctProb then -- octave probability slider + octProbSldr.val1 = pExtState.rndOctProb + else octProbSldr.val1 = m.rndOctProb - if pExtState.rndOctProb then -- write the new proj ext state - pExtState.rndOctProb = nil - end -- pExtState.noteSliders - pExtSaveStateF = true -- set the ext state save flag - end -- result + end end + + +-- sequencer gui functions --------------------------------------------------- -------------------------------------------------------------------------------- --- Sequencer --------------------------------------------------------------------------------- --- Sequencer button -sequenceBtn.onLClick = function() - local debug = false - if debug or m.debug then ConMsg("\nsequenceBtn.onLClick()") end + +sequenceBtn.onLClick = function() -- Sequencer button + if m.activeTake and m.mItem then m.seqShift = 0; m.seqShiftMin = 0; m.seqShiftMax = 0 -- reset shift on new sequence seqShiftVal.label = tostring(m.seqShift) + if m.seqF then SetSeqGridSizes(t_seqSliders) GenProbTable(m.preSeqProbTable, t_seqSliders, m.seqProbTable) @@ -1451,6 +1593,7 @@ sequenceBtn.onLClick = function() if m.seqRndNotesF then randomBtn.onLClick() -- call RandomiseNotes end + else -- not m.seqF GenAccentTable(m.accProbTable, seqAccRSldr, seqAccProbSldr) GenLegatoTable(m.legProbTable, seqLegProbSldr) @@ -1460,6 +1603,7 @@ sequenceBtn.onLClick = function() randomBtn.onLClick() -- call RandomiseNotes end end -- m.seqF + -- set project ext state if seqGridRad.val1 == 1 then -- 1/16 grid pExtState.seqGrid16 = {} @@ -1467,12 +1611,14 @@ sequenceBtn.onLClick = function() pExtState.seqGrid16[k] = v.val1 end end + if seqGridRad.val1 == 2 then -- 1/8 grid pExtState.seqGrid8 = {} for k, v in pairs (t_seqSliders) do pExtState.seqGrid8[k] = v.val1 end end + if seqGridRad.val1 == 3 then -- 1/4 grid pExtState.seqGrid4 = {} for k, v in pairs (t_seqSliders) do @@ -1484,28 +1630,26 @@ sequenceBtn.onLClick = function() pExtState.seqAccRSldrHi = seqAccRSldr.val2 pExtState.seqAccProb = seqAccProbSldr.val1 pExtState.seqLegProb = seqLegProbSldr.val1 - pExtSaveStateF = true end --m.activeTake end --- Sequencer options toggle logic -seqOptionsCb.onLClick = function() - local debug = false - if debug or m.debug then ConMsg("\nseqOptionsCb.onLClick()") end + +seqOptionsCb.onLClick = function() -- Sequencer options toggle logic + m.seqF = seqOptionsCb.val1[1] == 1 and true or false -- Generate m.seqFirstNoteF = seqOptionsCb.val1[2] == 1 and true or false -- 1st Note Always m.seqAccentF = seqOptionsCb.val1[3] == 1 and true or false -- Accent m.seqLegatoF = seqOptionsCb.val1[4] == 1 and true or false -- Legato m.seqRndNotesF = seqOptionsCb.val1[5] == 1 and true or false -- Randomise Notes m.seqRepeatF = seqOptionsCb.val1[6] == 1 and true or false -- Repeat + pExtState.seqOptionsCb = {m.seqF, m.seqFirstNoteF, m.seqAccentF, m.seqLegatoF, m.seqRndNotesF, m.seqRepeatF} pExtSaveStateF = true - if debug or m.debug then PrintTable(seqOptionsCb.val1) end + end --- Sequencer grid radio button -seqGridRad.onLClick = function() -- change grid size - local debug = false - if debug or m.debug then ConMsg("\nseqGridRad.onLClick()") end + +seqGridRad.onLClick = function() -- Sequencer grid radio button + if m.activeTake then if seqGridRad.val1 == 1 then -- 1/16 grid @@ -1555,39 +1699,114 @@ seqGridRad.onLClick = function() -- change grid size end -- m.activeTake end --- Sequencer shift left -seqShiftLBtn.onLClick = function() + +seqShiftLBtn.onLClick = function() -- Sequencer shift left + if not m.mItem then return end local gridSize = m.reaGrid * m.ppqn local itemLength = GetItemLength() m.seqShiftMin = -(math.floor(itemLength / gridSize)-1) + if m.seqShift <= m.seqShiftMin then m.seqShift = 0 else m.seqShift = m.seqShift - 1 end + seqShiftVal.label = tostring(m.seqShift) InsertNotes() end --- Sequencer shift right -seqShiftRBtn.onLClick = function() + +seqShiftRBtn.onLClick = function() -- Sequencer shift right + if not m.mItem then return end + local gridSize = m.reaGrid * m.ppqn local itemLength = GetItemLength() m.seqShiftMax = math.floor(itemLength / gridSize) - 1 + if m.seqShift >= m.seqShiftMax then m.seqShift = 0 else m.seqShift = m.seqShift + 1 end + seqShiftVal.label = tostring(m.seqShift) InsertNotes() end --- Set sequencer default options -function SetDefaultSeqOptions() - local debug = false - if debug or m.debug then ConMsg("SetDefaultSeqOptions()") end +seqSldrText.onRClick = function() -- Reset sequencer grid sliders + + gfx.x = gfx.mouse_x; gfx.y = gfx.mouse_y + local result = gfx.showmenu("Reset Sequence Sliders") + + if result == 1 then + if seqGridRad.val1 == 1 then -- 1/16ths + for k, v in pairs(t_seqSliders) do -- reset the sliders + v.val1 = m.seqGrid16[k] + end -- in pairs(t_seqSliders) + pExtState.seqGrid16 = nil + + elseif seqGridRad.val1 == 2 then -- 1/8ths + for k, v in pairs(t_seqSliders) do -- reset the sliders + v.val1 = m.seqGrid8[k] + end -- in pairs(t_seqSliders) + pExtState.seqGrid8 = nil + + elseif seqGridRad.val1 == 3 then -- 1/4ths + for k, v in pairs(t_seqSliders) do -- reset the sliders + v.val1 = m.seqGrid4[k] + end -- in pairs(t_seqSliders) + pExtState.seqGrid4 = nil + + end -- seqGridRad + pExtSaveStateF = true -- set the ext state save flag + end -- result +end + +seqAccSldrText.onRClick = function() -- Reset sequencer velocity slider + + gfx.x = gfx.mouse_x; gfx.y = gfx.mouse_y + local result = gfx.showmenu("Reset Accent Sliders") + + if result == 1 then + seqAccRSldr.val1 = m.accentLow + if pExtState.seqAccRSldrLo then pExtState.seqAccRSldrLo = nil end + seqAccRSldr.val2 = m.accentHigh + if pExtState.seqAccRSldrHi then pExtState.seqAccRSldrHi = nil end + seqAccProbSldr.val1 = m.accentProb + if pExtState.seqAccProb then pExtState.seqAccProb = nil end + + pExtSaveStateF = true -- set the ext state save flag + end -- result +end + +seqLegSldrText.onRClick = function() -- Reset sequencer legato slider + + gfx.x = gfx.mouse_x; gfx.y = gfx.mouse_y + local result = gfx.showmenu("Reset Legato Slider") + + if result == 1 then + seqLegProbSldr.val1 = m.legatoProb + if pExtState.seqLegProb then pExtState.seqLegProb = nil end + pExtSaveStateF = true + end -- result +end + +seqShiftText.onRClick = function() -- Reset sequencer shift + + gfx.x = gfx.mouse_x; gfx.y = gfx.mouse_y + local result = gfx.showmenu("Reset Note Shift") + + if result == 1 then + m.seqShift = 0; m.seqShiftMin = 0; m.seqShiftMax = 0 + seqShiftVal.label = tostring(m.seqShift) + InsertNotes() + end -- result +end + +-- defaults +function SetDefaultSeqOptions() -- Set sequencer default options -- if sequencer options were saved to project state, load them if pExtState.seqOptionsCb then @@ -1607,36 +1826,36 @@ function SetDefaultSeqOptions() seqOptionsCb.val1[5] = (true and m.seqRndNotesF) and 1 or 0 -- random notes seqOptionsCb.val1[6] = (true and m.seqRepeatF) and 1 or 0 -- repeat end --- Set default accent & legato sliders -function SetDefaultAccLegSliders() - local debug = false - if debug or m.debug then ConMsg("SetDefaultAccLegSliders()") end + +function SetDefaultAccLegSliders() -- Set default accent & legato sliders + -- if seq accent & legato sliders were saved to project state, load them if pExtState.seqAccRSldrLo then seqAccRSldr.val1 = pExtState.seqAccRSldrLo else seqAccRSldr.val1 = m.accentLow end + if pExtState.seqAccRSldrHi then seqAccRSldr.val2 = pExtState.seqAccRSldrHi else seqAccRSldr.val2 = m.accentHigh end + if pExtState.seqAccProb then seqAccProbSldr.val1 = pExtState.seqAccProb else seqAccProbSldr.val1 = m.accentProb end + if pExtState.seqLegProb then seqLegProbSldr.val1 = pExtState.seqLegProb else seqLegProbSldr.val1 = m.legatoProb end end --- Set default grid sliders -function SetDefaultSeqGridSliders() - local debug = false - if debug or m.debug then ConMsg("\nSetDefaultSeqGridSliders()") end + +function SetDefaultSeqGridSliders() -- Set default grid sliders GetReaperGrid(seqGridRad) SetSeqGridSizes(t_seqSliders) @@ -1674,121 +1893,43 @@ function SetDefaultSeqGridSliders() end end end -- pExtState.seqGrid4 - - if debug or m.debug then - for k, v in pairs(t_seqSliders) do - ConMsg("t_seqSliders.val1 (4) = " .. tostring(v.val1)) - end - end end --- Set default sequencer shift state -function SetDefaultSeqShift() - local debug = false - if debug or m.debug then ConMsg("SetDefaultSeqShift()") end + +function SetDefaultSeqShift() -- Set default sequencer shift state + m.seqShift = 0 m.seqShiftMin = 0 m.seqShiftMax = 0 seqShiftVal.label = tostring(m.seqShift) end --- Reset sequencer grid sliders -seqSldrText.onRClick = function() - local debug = false - if debug or m.debug then ConMsg("\nseqSldrText.onLClick()") end - gfx.x = gfx.mouse_x; gfx.y = gfx.mouse_y - local result = gfx.showmenu("Reset Sequence Sliders") - if result == 1 then - if seqGridRad.val1 == 1 then -- 1/16ths - for k, v in pairs(t_seqSliders) do -- reset the sliders - v.val1 = m.seqGrid16[k] - end -- in pairs(t_seqSliders) - pExtState.seqGrid16 = nil - - elseif seqGridRad.val1 == 2 then -- 1/8ths - for k, v in pairs(t_seqSliders) do -- reset the sliders - v.val1 = m.seqGrid8[k] - end -- in pairs(t_seqSliders) - pExtState.seqGrid8 = nil - - elseif seqGridRad.val1 == 3 then -- 1/4ths - for k, v in pairs(t_seqSliders) do -- reset the sliders - v.val1 = m.seqGrid4[k] - end -- in pairs(t_seqSliders) - pExtState.seqGrid4 = nil - - end -- seqGridRad - pExtSaveStateF = true -- set the ext state save flag - end -- result -end --- Reset sequencer velocity slider -seqAccSldrText.onRClick = function() - local debug = false - if debug or m.debug then ConMsg("\nseqAccSldrText.onLClick()") end - gfx.x = gfx.mouse_x; gfx.y = gfx.mouse_y - local result = gfx.showmenu("Reset Accent Sliders") - if result == 1 then - seqAccRSldr.val1 = m.accentLow - if pExtState.seqAccRSldrLo then pExtState.seqAccRSldrLo = nil end - seqAccRSldr.val2 = m.accentHigh - if pExtState.seqAccRSldrHi then pExtState.seqAccRSldrHi = nil end - seqAccProbSldr.val1 = m.accentProb - if pExtState.seqAccProb then pExtState.seqAccProb = nil end - pExtSaveStateF = true -- set the ext state save flag - end -- result -end --- Reset sequencer legato slider -seqLegSldrText.onRClick = function() - local debug = false - if debug or m.debug then ConMsg("\nseqLegSldrText.onLClick()") end - gfx.x = gfx.mouse_x; gfx.y = gfx.mouse_y - local result = gfx.showmenu("Reset Legato Slider") - if result == 1 then - seqLegProbSldr.val1 = m.legatoProb - if pExtState.seqLegProb then pExtState.seqLegProb = nil end - pExtSaveStateF = true - end -- result -end --- Reset sequencer shift -seqShiftText.onRClick = function() - local debug = false - if debug or m.debug then ConMsg("\nseqShiftText.onRClick") end - gfx.x = gfx.mouse_x; gfx.y = gfx.mouse_y - local result = gfx.showmenu("Reset Note Shift") - if result == 1 then - m.seqShift = 0; m.seqShiftMin = 0; m.seqShiftMax = 0 - seqShiftVal.label = tostring(m.seqShift) - InsertNotes() - end -- result -end +-- euclidiser gui functions -------------------------------------------------- -------------------------------------------------------------------------------- --- Euclidiser --------------------------------------------------------------------------------- --- Euclidiser button -euclidBtn.onLClick = function() - local debug = false - if debug or m.debug then ConMsg("\neuclidBtn.onLClick()") end + +euclidBtn.onLClick = function() -- Euclidiser button + if m.activeTake and m.mItem then if m.eucF then - if debug or m.debug then ConMsg("m.eucF = " .. tostring(m.eucF)) end GenAccentTable(m.accProbTable, seqAccRSldr, seqAccProbSldr) GetNotesFromTake() GenBjorklund(euclidPulsesSldr, euclidStepsSldr, euclidRotationSldr, m.accProbTable, seqAccRSldr) if m.eucRndNotesF then randomBtn.onLClick() -- call RandomiseNotes end + else -- not m.eucF - if debug or m.debug then ConMsg("m.eucF = " .. tostring(m.eucF)) end GenAccentTable(m.accProbTable, seqAccRSldr, seqAccProbSldr) GetNotesFromTake() GenNoteAttributes(m.eucAccentF, m.accProbTable, seqAccRSldr, false, m.legProbTable) + if m.eucRndNotesF then - if debug or m.debug then ConMsg("m.eucRndNotesF = " .. tostring(m.eucRndNotesF)) end randomBtn.onLClick() -- call RandomiseNotes end end -- m.eucF + -- set project ext state pExtState.eucSliders = {} for k, v in pairs(t_euclidSliders) do @@ -1797,30 +1938,27 @@ euclidBtn.onLClick = function() pExtSaveStateF = true end -- m.activeTake end --- Euclidiser options -eucOptionsCb.onLClick = function() - local debug = false - if debug or m.debug then ConMsg("\neucOptionsCb.onLClick()") end + +eucOptionsCb.onLClick = function() -- Euclidiser options + m.eucF = eucOptionsCb.val1[1] == 1 and true or false -- Generate m.eucAccentF = eucOptionsCb.val1[2] == 1 and true or false -- Accent m.eucRndNotesF = eucOptionsCb.val1[3] == 1 and true or false -- Randomise notes pExtState.eucOptionsCb = {m.eucF, m.eucAccentF, m.eucRndNotesF} pExtSaveStateF = true - if debug or m.debug then PrintTable(eucOptionsCb.val1) end + end --- Euclid pulses slider -euclidPulsesSldr.onMove = function() - local debug = false - if debug or m.debug then ConMsg("euclidPlusesSldr.onMove()") end + +euclidPulsesSldr.onMove = function() -- Euclid pulses slider + if euclidPulsesSldr.val1 > euclidStepsSldr.val1 then -- pulses > steps euclidStepsSldr.val1 = euclidPulsesSldr.val1 euclidRotationSldr.max = euclidStepsSldr.val1 end end --- Euclid steps slider -euclidStepsSldr.onMove = function() - local debug = false - if debug or m.debug then ConMsg("euclidStepsSldr.onMove()") end + +euclidStepsSldr.onMove = function() -- Euclid steps slider + if euclidStepsSldr.val1 < euclidPulsesSldr.val1 then -- steps < pulses euclidPulsesSldr.val1 = euclidStepsSldr.val1 end @@ -1832,10 +1970,9 @@ euclidStepsSldr.onMove = function() euclidRotationSldr.max = euclidStepsSldr.val1 end end --- Euclid rotation slider -euclidRotationSldr.onMove = function() - local debug = false - if debug or m.debug then ConMsg("euclidRotationSldr.onMove()") end + +euclidRotationSldr.onMove = function() -- Euclid rotation slider + euclidRotationSldr.max = euclidStepsSldr.val1 if euclidRotationSldr.val1 > euclidStepsSldr.val1 then euclidRotationSldr.val1 = euclidStepsSldr.val1 @@ -1843,10 +1980,22 @@ euclidRotationSldr.onMove = function() end end --- Set default euclid options -function SetDefaultEucOptions() - local debug = false - if debug or m.debug then ConMsg("SetDefaultEucOptions()") end +txtEuclidLabel.onRClick = function() -- Reset euclidean sliders + + gfx.x = gfx.mouse_x; gfx.y = gfx.mouse_y + local result = gfx.showmenu("Reset Euclid Sliders") + + if result == 1 then + euclidPulsesSldr.val1 = m.eucPulses + euclidStepsSldr.val1 = m.eucSteps + euclidRotationSldr.val1 = m.eucRot + pExtSaveStateF = true + pExtState.eucSliders = nil + end -- result +end + +function SetDefaultEucOptions() -- Set default euclid options + -- if euclidean options were saved to project state, load them if pExtState.eucOptionsCb then m.eucF = pExtState.eucOptionsCb[1] == true and true or false @@ -1858,10 +2007,9 @@ function SetDefaultEucOptions() eucOptionsCb.val1[2] = (true and m.eucAccentF) and 1 or 0 -- accents eucOptionsCb.val1[3] = (true and m.eucRndNotesF) and 1 or 0 -- randomise notes end --- Set default euclid sliders -function SetDefaultEucSliders() - local debug = false - if debug or m.debug then ConMsg("SetDefaultEucSliders()") end + +function SetDefaultEucSliders() -- Set default euclid sliders + -- if euclidean sliders were saved to project state, load them if pExtState.eucSliders then for k, v in pairs(t_euclidSliders) do @@ -1874,25 +2022,13 @@ function SetDefaultEucSliders() end -- load pExtState end --- Reset euclidean sliders -txtEuclidLabel.onRClick = function() - local debug = false - if debug or m.debug then ConMsg("\ntxtEuclidLabel.onLClick()") end - gfx.x = gfx.mouse_x; gfx.y = gfx.mouse_y - local result = gfx.showmenu("Reset Euclid Sliders") - if result == 1 then - euclidPulsesSldr.val1 = m.eucPulses - euclidStepsSldr.val1 = m.eucSteps - euclidRotationSldr.val1 = m.eucRot - pExtSaveStateF = true - pExtState.eucSliders = nil - end -- result -end + +-- draw GUI ------------------------------------------------------------------ -------------------------------------------------------------------------------- --- Draw GUI --------------------------------------------------------------------------------- + function DrawGUI() + for key, winElms in pairs(t_winElements) do winElms:draw() end --for key, frame in pairs(t_Frames) do frame:draw() end for key, check in pairs(t_Checkboxes) do check:draw() end @@ -1905,35 +2041,28 @@ function DrawGUI() for key, ssldrs in pairs(t_seqSliders) do ssldrs:draw() end for key, esldrs in pairs(t_euclidSliders) do esldrs:draw() end for key, textb in pairs(t_Textboxes) do textb:draw() end + end --------------------------------------------------------------------------------- --- GUI Functions END --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- --- GUI END --------------------------------------------------------------------------------- + + +-- init ---------------------------------------------------------------------- -------------------------------------------------------------------------------- --- INIT --------------------------------------------------------------------------------- --------------------------------------------------------------------------------- --- InitMidiExMachina --------------------------------------------------------------------------------- -function InitMidiExMachina() + +function Init() + math.randomseed(os.time()) for i = 1, 15 do math.random() end -- lua quirk, first random call always returns the same value... reaper.ClearConsole() - local debug = false - if debug or m.debug then ConMsg("InitMidiExMachina()") end -- grab the midi editor, and active take m.activeEditor = reaper.MIDIEditor_GetActive() if m.activeEditor then m.activeTake = reaper.MIDIEditor_GetTake(m.activeEditor) __ = NewNoteBuf() - if not m.activeTake then ConMsg("InitMidiExMachina() - No Active Take") end + if not m.activeTake then ConMsg("Init() - No Active Take") end else - ConMsg("InitMidiMachina() - No Active MIDI Editor") + ConMsg("Init() - No Active MIDI Editor") end -- m.activeEditor -- Load ProjectExtState @@ -1952,33 +2081,34 @@ function InitMidiExMachina() SetDefaultSeqGridSliders(); SetDefaultAccLegSliders() SetDefaultEucOptions(); SetDefaultEucSliders() m.mItem = reaper.GetSelectedMediaItem(0, 0) - --GetItemLength() + GetNotesFromTake() -- grab the original note data (if any...) - if debug or m.debug then ConMsg("End InitMidiExMachina()\n") end end --------------------------------------------------------------------------------- --- InitGFX --------------------------------------------------------------------------------- + function InitGFX() - local debug = false - if debug or m.debug then ConMsg("InitGFX()") end -- Init window ------ gfx.clear = RGB2Packed(table.unpack(m.win_bg)) gfx.init(m.win_title, m.win_w * e.gScale, m.win_h * e.gScale, m.win_dockstate, m.win_x, m.win_y) + -- Last mouse position and state gLastMouseCap, gLastMouseX, gLastMouseY = 0, 0, 0 gMouseOX, gMouseOY = -1, -1 end + + + +-- main ---------------------------------------------------------------------- -------------------------------------------------------------------------------- --- Mainloop --------------------------------------------------------------------------------- + function MainLoop() + -- Update mouse state and position if gfx.mouse_cap & 1 == 1 and gLastMouseCap & 1 == 0 or -- L mouse gfx.mouse_cap & 2 == 2 and gLastMouseCap & 2 == 0 or -- R mouse gfx.mouse_cap & 64 == 64 and gLastMouseCap & 64 == 0 then -- M mouse gMouseOX, gMouseOY = gfx.mouse_x, gfx.mouse_y end + -- Set modifier keys Ctrl = gfx.mouse_cap & 4 == 4 Shift = gfx.mouse_cap & 8 == 8 @@ -1991,6 +2121,7 @@ function MainLoop() m.win_y = pExtState.win_y pExtSaveStateF = true end + -- if resized, set scale flag and reset gfx if m.zoomF == true then e.gScaleState = true @@ -2027,7 +2158,9 @@ function MainLoop() m.lmItem = m.mItem m.lactiveTake = m.activeTake m.mItem = reaper.GetSelectedMediaItem(0, 0) + if m.mItem then + if m.mItem ~= m.lmItem then m.seqShift = 0; m.seqShiftMin = 0; m.seqShiftMax = 0 seqShiftVal.label = tostring(m.seqShift) @@ -2037,25 +2170,16 @@ function MainLoop() end m.activeEditor = reaper.MIDIEditor_GetActive() + if m.activeEditor then + m.activeTake = reaper.MIDIEditor_GetTake(m.activeEditor) if m.activeTake ~= m.lactiveTake then GetNotesFromTake() end if m.lmItem == nil and m.mItem then GetNotesFromTake() end + if m.activeTake then ShowMessage(msgText, 0) -- clear old messages - -- check for changes in the active take if the "Permute" scale is selected - if scaleDrop.val2[scaleDrop.val1] == "Permute" then - __, pHash = reaper.MIDI_GetHash(m.activeTake, false, 0) - if m.pHash ~= pHash then - SetScale("Permute", m.scales, m.preNoteProbTable) - UpdateSliderLabels(t_noteSliders, m.preNoteProbTable) - m.pHash = pHash - end -- m.pHash - -- don't allow any note options that might upset permute... - noteOptionsCb.val1[2] = 0; m.rndFirstNoteF = false - noteOptionsCb.val1[3] = 0; m.rndOctX2F = false - end -- scaleDrop -- check for grid changes local grid = m.reaGrid m.reaGrid, __, __ = reaper.MIDI_GetGrid(m.activeTake) @@ -2067,6 +2191,7 @@ function MainLoop() ShowMessage(msgText, 1) m.activeTake = nil end -- m.activeTake + else -- handle m.activeEditor error -- pop up error message - switch layer on textbox element ShowMessage(msgText, 1) @@ -2079,10 +2204,10 @@ function MainLoop() end end + +-- run ----------------------------------------------------------------------- -------------------------------------------------------------------------------- --- RUN --------------------------------------------------------------------------------- -InitMidiExMachina() + +Init() InitGFX() MainLoop() --------------------------------------------------------------------------------- \ No newline at end of file diff --git a/MIDI Editor/MIDI Ex Machina/persistence.lua b/MIDI Editor/MIDI Ex Machina/persistence.lua deleted file mode 100644 index 2625520..0000000 --- a/MIDI Editor/MIDI Ex Machina/persistence.lua +++ /dev/null @@ -1,228 +0,0 @@ ---[[ -@description Persistence Library -@about - #### Table Serialization Library by Gerhard Roethlin - Provides - - persistence.store(path, ...): store arbitrary items to the file - - persistence.load(path): load files that were previously stored - - Limitations - - Does not support userdata, threads, or most function values - - Function export is not portable -@noindex -@version 1.0 -@author Gerhard Roethlin - -MIT License (see bottom of file) ---]] - ---[[ Provides ]] --- persistence.store(path, ...): Stores arbitrary items to the file at the given path --- persistence.load(path): Loads files that were previously stored with store and returns them - ---[[ Limitations ]] --- Does not export userdata, threads or most function values --- Function export is not portable - ---[[ License: MIT (see bottom) ]] - --------------------------------------------------------------------------------- --- Private methods -local write, writeIndent, writers, refCount; --------------------------------------------------------------------------------- - -persistence = -{ - store = function (path, ...) - local file, e - if type(path) == "string" then - -- Path, open a file - file, e = io.open(path, "w") - if not file then - return error(e) - end - else - -- Just treat it as file - file = path - end - - local n = select("#", ...) - -- Count references - local objRefCount = {} -- Stores reference that will be exported - for i = 1, n do - refCount(objRefCount, (select(i,...))) - end - -- Export Objects with more than one ref and assign name - -- First, create empty tables for each - local objRefNames = {} - local objRefIdx = 0 - file:write("-- Persistent Data\n") - file:write("local multiRefObjects = {\n") - for obj, count in pairs(objRefCount) do - if count > 1 then - objRefIdx = objRefIdx + 1 - objRefNames[obj] = objRefIdx - file:write("{};") -- table objRefIdx - end - end - file:write("\n} -- multiRefObjects\n") - -- Then fill them (this requires all empty multiRefObjects to exist) - for obj, idx in pairs(objRefNames) do - for k, v in pairs(obj) do - file:write("multiRefObjects["..idx.."][") - write(file, k, 0, objRefNames) - file:write("] = ") - write(file, v, 0, objRefNames) - file:write(";\n") - end - end - -- Create the remaining objects - for i = 1, n do - file:write("local ".."obj"..i.." = ") - write(file, (select(i,...)), 0, objRefNames) - file:write("\n") - end - -- Return them - if n > 0 then - file:write("return obj1") - for i = 2, n do - file:write(" ,obj"..i) - end - file:write("\n") - else - file:write("return\n") - end - file:close() - end; - - load = function (path) - local f, e = loadfile(path); - if f then - return f() - else - return nil, e - end - end -} - --- Private methods --------------------------------------------------------------------------------- --- write thing (dispatcher) -write = function (file, item, level, objRefNames) - writers[type(item)](file, item, level, objRefNames) -end --------------------------------------------------------------------------------- --- write indent -writeIndent = function (file, level) - for i = 1, level do - file:write("\t") - end -end --------------------------------------------------------------------------------- --- recursively count references -refCount = function (objRefCount, item) - -- only count reference types (tables) - if type(item) == "table" then - -- Increase ref count - if objRefCount[item] then - objRefCount[item] = objRefCount[item] + 1 - else - objRefCount[item] = 1 - -- If first encounter, traverse - for k, v in pairs(item) do - refCount(objRefCount, k) - refCount(objRefCount, v) - end - end - end -end --------------------------------------------------------------------------------- --- Format items for the purpose of restoring -writers = { - ["nil"] = function (file, item) - file:write("nil") - end; - ["number"] = function (file, item) - file:write(tostring(item)) - end; - ["string"] = function (file, item) - file:write(string.format("%q", item)); - end; - ["boolean"] = function (file, item) - if item then - file:write("true") - else - file:write("false") - end - end; - ["table"] = function (file, item, level, objRefNames) - local refIdx = objRefNames[item] - if refIdx then - -- Table with multiple references - file:write("multiRefObjects["..refIdx.."]") - else - -- Single use table - file:write("{\n") - for k, v in pairs(item) do - writeIndent(file, level+1) - file:write("[") - write(file, k, level+1, objRefNames) - file:write("] = ") - write(file, v, level+1, objRefNames) - file:write(";\n") - end - writeIndent(file, level) - file:write("}") - end - end; - ["function"] = function (file, item) - -- Does only work for "normal" functions, not those - -- with upvalues or c functions - local dInfo = debug.getinfo(item, "uS") - if dInfo.nups > 0 then - file:write("nil --[[functions with upvalue not supported]]") - elseif dInfo.what ~= "Lua" then - file:write("nil --[[non-lua function not supported]]") - else - local r, s = pcall(string.dump,item) - if r then - file:write(string.format("loadstring(%q)", s)) - else - file:write("nil --[[function could not be dumped]]") - end - end - end; - ["thread"] = function (file, item) - file:write("nil --[[thread]]\n") - end; - ["userdata"] = function (file, item) - file:write("nil --[[userdata]]\n") - end; -} --------------------------------------------------------------------------------- -return persistence --------------------------------------------------------------------------------- ---[[ - Copyright (c) 2010 Gerhard Roethlin - - Permission is hereby granted, free of charge, to any person - obtaining a copy of this software and associated documentation - files (the "Software"), to deal in the Software without - restriction, including without limitation the rights to use, - copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following - conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - OTHER DEALINGS IN THE SOFTWARE. -]] \ No newline at end of file diff --git a/README.md b/README.md index 11a41fa..864525c 100644 --- a/README.md +++ b/README.md @@ -7,5 +7,6 @@ See the [Wiki](https://github.com/RobU23/ReaScripts/wiki) for details This repository is [ReaPack](https://reapack.com/) compatible - The import URL is `https://github.com/RobU23/ReaScripts/raw/master/index.xml` --- -*If you find any of this stuff useful, and would like to buy me a beer, the internets has the answer...* -[Beer Donation](https://www.paypal.me/RobUrquhart) +*Please not that this script is not actively maintained. Bad bugs may get fixed from time to time, but there will be no further development of this version.* + +*A more modular, more maintainable rewrite is in progress, but... don't hold your breath :)* diff --git a/Stop_no_item_error_patch.diff b/Stop_no_item_error_patch.diff deleted file mode 100644 index b89299c..0000000 --- a/Stop_no_item_error_patch.diff +++ /dev/null @@ -1,317 +0,0 @@ -diff --git a/MIDI Editor/MIDI Ex Machina/RobU - MIDI Ex Machina.lua b/MIDI Editor/MIDI Ex Machina/RobU - MIDI Ex Machina.lua -index 1e38c54..da244f3 100644 ---- a/MIDI Editor/MIDI Ex Machina/RobU - MIDI Ex Machina.lua -+++ b/MIDI Editor/MIDI Ex Machina/RobU - MIDI Ex Machina.lua -@@ -57,7 +57,7 @@ local p = require 'persistence' -- currently unused, i.e. no preset save, load, - -------------------------------------------------------------------------------- - m = {} -- all ex machina data - -- user changeable defaults are marked with "(option)" --m.debug = false -+m.debug = true - m.OS = reaper.GetOS() - -- window - m.win_title = "RobU : MIDI Ex Machina - v1.3.4"; m.win_dockstate = 0 -@@ -359,7 +359,7 @@ local function UndoNoteBuf() - end - end - --local function PurgeNoteBuf() -+local function PurgeNoteBuf(idx) - -- PurgeNoteBuf() - purge all note buffers from current+1 to end - local debug = false - if debug or m.debug then -@@ -383,25 +383,26 @@ function GetItemLength() - local debug = false - if debug or m.debug then ConMsg("GetItemLength()") end - -- mItem = reaper.GetSelectedMediaItem(0, 0) -- mItemLen = reaper.GetMediaItemInfo_Value(mItem, "D_LENGTH") -- mBPM, mBPI = reaper.GetProjectTimeSignature2(0) -- msPerMin = 60000 -- msPerQN = msPerMin / mBPM -- numQNPerItem = (mItemLen * 1000) / msPerQN -- numBarsPerItem = numQNPerItem / 4 -- ItemPPQN = numQNPerItem * m.ppqn -- if debug or m.debug then -- ConMsg("ItemLen (ms) = " .. mItemLen) -- ConMsg("mBPM = " .. mBPM) -- ConMsg("MS Per QN = " .. msPerQN) -- ConMsg("Num of QN = " .. numQNPerItem) -- ConMsg("Num of Bar = " .. numBarsPerItem) -- ConMsg("Item size ppqn = " .. ItemPPQN .. "\n") -- end -- mItemTake = reaper.GetTake(mItem, 0) -- should fix looped items -- ItemPPQN = reaper.BR_GetMidiSourceLenPPQ(mItemTake) -- thanks Thrash! -- return ItemPPQN -+ --mItem = reaper.GetSelectedMediaItem(0, 0) -+ mItemLen = reaper.GetMediaItemInfo_Value(m.mItem, "D_LENGTH") -+ mBPM, mBPI = reaper.GetProjectTimeSignature2(0) -+ msPerMin = 60000 -+ msPerQN = msPerMin / mBPM -+ numQNPerItem = (mItemLen * 1000) / msPerQN -+ numBarsPerItem = numQNPerItem / 4 -+ ItemPPQN = numQNPerItem * m.ppqn -+ if debug or m.debug then -+ ConMsg("ItemLen (ms) = " .. mItemLen) -+ ConMsg("mBPM = " .. mBPM) -+ ConMsg("MS Per QN = " .. msPerQN) -+ ConMsg("Num of QN = " .. numQNPerItem) -+ ConMsg("Num of Bar = " .. numBarsPerItem) -+ ConMsg("Item size ppqn = " .. ItemPPQN .. "\n") -+ end -+ mItemTake = reaper.GetTake(m.mItem, 0) -- should fix looped items -+ ItemPPQN = reaper.BR_GetMidiSourceLenPPQ(mItemTake) -- thanks Thrash! -+ return ItemPPQN -+ - end - - function GetReaperGrid(gridRad) -@@ -447,7 +448,7 @@ end - -- GetNotesFromTake() - fill a note buffer from the active take - -------------------------------------------------------------------------------- - function GetNotesFromTake() -- local debug = false -+ local debug = true - if debug or m.debug then ConMsg("GetNotesFromTake()") end - local i, t - if m.activeTake then -@@ -468,6 +469,7 @@ function GetNotesFromTake() - t[i][8] = velocity - end -- for i - end -- num_notes -+ PrintNotes(GetNoteBuf()) - else -- no active take - if debug or m.debug then ConMsg("No Active Take") end - end -- m.activeTake -@@ -855,26 +857,7 @@ function GenNoteAttributes(accF, accProbTable, accSlider, legF, legProbTable) - PurgeNoteBuf() - InsertNotes() - end ---------------------------------------------------------------------------------- ---- SetNotes - arg notebuf t1; set notes in the active take ---------------------------------------------------------------------------------- --function SetNotes() -- local debug = false -- if debug or m.debug then ConMsg("SetNotes()") end -- local i = 1 -- if m.activeTake then -- local t1 = GetNoteBuf() -- while t1[i] do -- reaper.MIDI_SetNote(m.activeTake, i-1, t1[i][1], t1[i][2], t1[i][3], t1[i][4], t1[i][6], t1[i][7], t1[i][8], __) -- --1=selected, 2=muted, 3=startppq, 4=endppq, 5=len, 6=chan, 7=pitch, 8=vel, noSort) -- i = i + 1 -- end -- while t1[i] -- reaper.MIDI_Sort(m.activeTake) -- reaper.MIDIEditor_OnCommand(m.activeEditor, 40435) -- all notes off -- else -- if debug or m.debug then ConMsg("No Active Take") end -- end -- m.activeTake --end -+ - -------------------------------------------------------------------------------- - -- InsertNotes(note_buffer) - insert notes in the active take - -------------------------------------------------------------------------------- -@@ -883,7 +866,7 @@ function InsertNotes() - if debug or m.debug then ConMsg("\nInsertNotes()") end - DeleteNotes() - local i = 1 -- if m.activeTake then -+ if m.activeTake and m.mItem then - local noteLength = 0 - local gridSize = m.reaGrid * m.ppqn - local itemLength = GetItemLength() -@@ -926,33 +909,14 @@ function InsertNotes() - if debug or m.debug then ConMsg("No Active Take") end - end -- m.activeTake - end ---------------------------------------------------------------------------------- ---- InsertNotes(note_buffer) - insert notes in the active take ---------------------------------------------------------------------------------- --function InsertNotesOld() -- local debug = false -- if debug or m.debug then ConMsg("InsertNotes()") end -- DeleteNotes() -- local i = 1 -- if m.activeTake then -- local t1 = GetNoteBuf() -- while t1[i] do -- reaper.MIDI_InsertNote(m.activeTake, t1[i][1], t1[i][2], t1[i][3], t1[i][4], t1[i][6], t1[i][7], t1[i][8], false) -- --1=selected, 2=muted, 3=startppq, 4=endppq, 5=len, 6=chan, 7=pitch, 8=vel, noSort) -- i = i + 1 -- end -- while t1[i] -- reaper.MIDI_Sort(m.activeTake) -- reaper.MIDIEditor_OnCommand(m.activeEditor, 40435) -- all notes off -- else -- if debug or m.debug then ConMsg("No Active Take") end -- end -- m.activeTake --end -+ - -------------------------------------------------------------------------------- - -- PrintNotes - arg note_buffer t; print note_buffer to reaper console - -------------------------------------------------------------------------------- - function PrintNotes(t) -- debug code - local debug = false - if debug or m.debug then ConMsg("PrintNotes()") end -+ if not t then return end - local i = 1 - local str = "sel \t mut \t s_ppq \t e_ppq \t leng \t chan \t pitch \t vel \n" - while t[i] do -@@ -991,7 +955,10 @@ function ShowMessage(tb, msgNum) - tb.label = "" - elseif msgNum == 1 then - tb.tab = 0 -- tb.label = "No Active Take" -+ tb.label = "MIDI Editor Closed" -+ elseif msgNum == 2 then -+ tb.tab = 0 -+ tb.label = "Please select a MIDI Item in the Arrange Window" - end - e.gScaleState = true - end -@@ -1300,7 +1267,7 @@ end - randomBtn.onLClick = function() - local debug = false - if debug or m.debug then ConMsg("\nrandomBtn.onLClick()") end -- if m.activeTake then -+ if m.activeTake and m.mItem then - m.seqShift = 0; m.seqShiftMin = 0; m.seqShiftMax = 0 -- reset shift - seqShiftVal.label = tostring(m.seqShift) - GenProbTable(m.preNoteProbTable, t_noteSliders, m.noteProbTable) -@@ -1470,7 +1437,7 @@ end - sequenceBtn.onLClick = function() - local debug = false - if debug or m.debug then ConMsg("\nsequenceBtn.onLClick()") end -- if m.activeTake then -+ if m.activeTake and m.mItem then - m.seqShift = 0; m.seqShiftMin = 0; m.seqShiftMax = 0 -- reset shift on new sequence - seqShiftVal.label = tostring(m.seqShift) - if m.seqF then -@@ -1589,6 +1556,7 @@ seqGridRad.onLClick = function() -- change grid size - end - -- Sequencer shift left - seqShiftLBtn.onLClick = function() -+ if not m.mItem then return end - local gridSize = m.reaGrid * m.ppqn - local itemLength = GetItemLength() - m.seqShiftMin = -(math.floor(itemLength / gridSize)-1) -@@ -1602,6 +1570,7 @@ seqShiftLBtn.onLClick = function() - end - -- Sequencer shift right - seqShiftRBtn.onLClick = function() -+ if not m.mItem then return end - local gridSize = m.reaGrid * m.ppqn - local itemLength = GetItemLength() - m.seqShiftMax = math.floor(itemLength / gridSize) - 1 -@@ -1800,7 +1769,7 @@ end - euclidBtn.onLClick = function() - local debug = false - if debug or m.debug then ConMsg("\neuclidBtn.onLClick()") end -- if m.activeTake then -+ if m.activeTake and m.mItem then - if m.eucF then - if debug or m.debug then ConMsg("m.eucF = " .. tostring(m.eucF)) end - GenAccentTable(m.accProbTable, seqAccRSldr, seqAccProbSldr) -@@ -1981,8 +1950,8 @@ function InitMidiExMachina() - SetDefaultSeqOptions(); SetDefaultSeqShift() - SetDefaultSeqGridSliders(); SetDefaultAccLegSliders() - SetDefaultEucOptions(); SetDefaultEucSliders() -- -- GetItemLength() -+ m.mItem = reaper.GetSelectedMediaItem(0, 0) -+ --GetItemLength() - GetNotesFromTake() -- grab the original note data (if any...) - if debug or m.debug then ConMsg("End InitMidiExMachina()\n") end - end -@@ -2053,41 +2022,58 @@ function MainLoop() - -- Update Reaper GFX - gfx.update() - -- -- check for midi editor and take -- m.activeEditor = reaper.MIDIEditor_GetActive() -- if m.activeEditor then -- m.activeTake = reaper.MIDIEditor_GetTake(m.activeEditor) -- if m.activeTake then -- ShowMessage(msgText, 0) -- clear old messages -- -- check for changes in the active take if the "Permute" scale is selected -- if scaleDrop.val2[scaleDrop.val1] == "Permute" then -- __, pHash = reaper.MIDI_GetHash(m.activeTake, false, 0) -- if m.pHash ~= pHash then -- SetScale("Permute", m.scales, m.preNoteProbTable) -- UpdateSliderLabels(t_noteSliders, m.preNoteProbTable) -- m.pHash = pHash -- end -- m.pHash -- -- don't allow any note options that might upset permute... -- noteOptionsCb.val1[2] = 0; m.rndFirstNoteF = false -- noteOptionsCb.val1[3] = 0; m.rndOctX2F = false -- end -- scaleDrop -- -- check for grid changes -- local grid = m.reaGrid -- m.reaGrid, __, __ = reaper.MIDI_GetGrid(m.activeTake) -- if grid ~= m.reaGrid then -- GetReaperGrid(seqGridRad) -- seqGridRad.onLClick() -- update the sequence grid sizes -- end -- grid -- else -- handle m.activeTake error -- ShowMessage(msgText, 1) -+ -- check for selected item, midi editor and take -+ m.lmItem = m.mItem -+ m.mItem = reaper.GetSelectedMediaItem(0, 0) -+ if m.mItem then -+ if m.mItem ~= m.lmItem then -+ m.mItemNew = true -+ m.seqShift = 0; m.seqShiftMin = 0; m.seqShiftMax = 0 -+ seqShiftVal.label = tostring(m.seqShift) -+ m.notebuf.i = 0 -- brutal hack -+ PurgeNoteBuf(); NewNoteBuf() -+ else -+ m.mItemNew = false -+ end -+ -+ m.activeEditor = reaper.MIDIEditor_GetActive() -+ if m.activeEditor then -+ m.activeTake = reaper.MIDIEditor_GetTake(m.activeEditor) -+ if m.activeTake then -+ ShowMessage(msgText, 0) -- clear old messages -+ -- check for changes in the active take if the "Permute" scale is selected -+ if scaleDrop.val2[scaleDrop.val1] == "Permute" then -+ __, pHash = reaper.MIDI_GetHash(m.activeTake, false, 0) -+ if m.pHash ~= pHash then -+ SetScale("Permute", m.scales, m.preNoteProbTable) -+ UpdateSliderLabels(t_noteSliders, m.preNoteProbTable) -+ m.pHash = pHash -+ end -- m.pHash -+ -- don't allow any note options that might upset permute... -+ noteOptionsCb.val1[2] = 0; m.rndFirstNoteF = false -+ noteOptionsCb.val1[3] = 0; m.rndOctX2F = false -+ end -- scaleDrop -+ -- check for grid changes -+ local grid = m.reaGrid -+ m.reaGrid, __, __ = reaper.MIDI_GetGrid(m.activeTake) -+ if grid ~= m.reaGrid then -+ GetReaperGrid(seqGridRad) -+ seqGridRad.onLClick() -- update the sequence grid sizes -+ end -- grid -+ else -- handle m.activeTake error -+ ShowMessage(msgText, 1) -+ m.activeTake = nil -+ end -- m.activeTake -+ else -- handle m.activeEditor error -+ -- pop up error message - switch layer on textbox element -+ ShowMessage(msgText, 1) -+ m.activeEditor = nil - m.activeTake = nil -- end -- m.activeTake -- else -- handle m.activeEditor error -- -- pop up error message - switch layer on textbox element -- ShowMessage(msgText, 1) -- m.activeEditor = nil -- m.activeTake = nil -- end -- m.activeEditor -+ end -- m.activeEditor -+ else -+ ShowMessage(msgText, 2) -+ m.mItem = nil -+ end - end - - -------------------------------------------------------------------------------- diff --git a/index.xml b/index.xml index f4f3840..a68035d 100644 --- a/index.xml +++ b/index.xml @@ -1,27 +1,27 @@ - + - https://github.com/RobU23/ReaScripts/raw/6fc14048fee05b74b2888e5a5a69061eabe3630f/MIDI%20Editor/MIDI%20Ex%20Machina/euclid.lua https://github.com/RobU23/ReaScripts/raw/6fc14048fee05b74b2888e5a5a69061eabe3630f/MIDI%20Editor/MIDI%20Ex%20Machina/persistence.lua + + + https://github.com/RobU23/ReaScripts/raw/e697cda55c3306c429bcc1c40d89736ab537f20d/MIDI%20Editor/MIDI%20Ex%20Machina/RobU%20-%20MIDI%20Ex%20Machina.lua + https://github.com/RobU23/ReaScripts/raw/e697cda55c3306c429bcc1c40d89736ab537f20d/MIDI%20Editor/MIDI%20Ex%20Machina/eGUI.lua + https://github.com/RobU23/ReaScripts/raw/e697cda55c3306c429bcc1c40d89736ab537f20d/MIDI%20Editor/MIDI%20Ex%20Machina/euclid.lua + https://github.com/RobU23/ReaScripts/raw/e697cda55c3306c429bcc1c40d89736ab537f20d/MIDI%20Editor/MIDI%20Ex%20Machina/persistence.lua + + + + https://github.com/RobU23/ReaScripts/raw/0692c3bb7ceaa9490f27172749f0a5af51c992bc/MIDI%20Editor/MIDI%20Ex%20Machina/RobU%20-%20MIDI%20Ex%20Machina.lua + https://github.com/RobU23/ReaScripts/raw/0692c3bb7ceaa9490f27172749f0a5af51c992bc/MIDI%20Editor/MIDI%20Ex%20Machina/eGUI.lua + https://github.com/RobU23/ReaScripts/raw/0692c3bb7ceaa9490f27172749f0a5af51c992bc/MIDI%20Editor/MIDI%20Ex%20Machina/euclid.lua +