-
-
Save pointofpresence/ba8dce7f8a286b6ab4ae41fbe1dba777 to your computer and use it in GitHub Desktop.
protoplug autoacc v17
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
C0-B1 - CHORDS | |
11.0: On new chord split logic | |
12.0: Random chord mode | |
13.0: Near note logic | |
14.0: Bypass chord mode | |
15.0: Octave shift | |
16.0: Transpose | |
17.0: Scaler | |
]]-- | |
require "include/protoplug" | |
-- HELPERS | |
-- generic table search function | |
table.find = function(tbl, val) | |
for k, v in pairs(tbl) do | |
if v == val then | |
return k | |
end | |
end | |
end | |
function tLen(T) | |
local count = 0 | |
for _ in pairs(T) do count = count + 1 end | |
return count | |
end | |
function dumpList(o) | |
if type(o) == 'table' then | |
local s = '{ ' | |
for k, v in pairs(o) do | |
if type(k) ~= 'number' then | |
k = '"'..k..'"=' | |
else | |
k = k .. '=' | |
end | |
s = s .. k .. dumpList(v) .. ',' | |
end | |
return s .. ' } ' | |
else | |
return tostring(o) | |
end | |
end | |
print("\n\n\n\n\n\n") | |
local MIN_NOTE = 0 | |
local MAX_NOTE = 131 | |
local CHORD_MODE = { | |
DEFAULT = 'Default', | |
BASS = 'Bass', | |
RANDOM = 'Random', | |
BYPASS = 'Bypass' | |
} | |
local CHORD_MODE_DEFAULT = CHORD_MODE.DEFAULT | |
local ON_NEW_CHORD = { | |
SPLIT = 'Split', | |
STOP = 'Stop', | |
DONOTHING = 'Do nothing' | |
} | |
local ON_NEW_CHORD_DEFAULT = ON_NEW_CHORD.SPLIT | |
local NEAR_NOTE_MODE = { | |
OFF = 'Off', | |
ON = 'On' | |
} | |
local NEAR_NOTE_MODE_DEFAULT = NEAR_NOTE_MODE.OFF | |
-- scale list with note transposition tables | |
local SCALES = { | |
{ name="None", keys={0,0,0,0,0,0,0,0,0,0,0,0} }, | |
{ name="Natural Major", keys={0,-1,0,-1,0,0,-1,0,-1,0,-1,0} }, | |
{ name="Natural Minor", keys={0,-1,0,0,-1,0,-1,0,0,-1,0,-1} }, | |
{ name="Pentatonic Major", keys={0,-1,0,-1,0,-1,-1,0,-1,0,-1,1} }, | |
{ name="Pentatonic Minor", keys={0,-1,-1,0,-1,0,-1,0,-1,-1,0,-1} }, | |
{ name="Egyptian Pentatonic", keys={0,-1,0,-1,1,0,-1,0,-1,-1,0,-1} }, | |
{ name="Blues Major", keys={0,-1,0,0,0,-1,1,0,-1,0,-1,1} }, | |
{ name="Blues Minor", keys={0,-1,1,0,-1,0,0,0,-1,-1,0,-1} }, | |
{ name="Whole Tone", keys={0,-1,0,-1,0,-1,0,-1,0,-1,0,-1} }, | |
{ name="Augmented", keys={0,-1,1,0,0,-1,1,0,0,-1,1,0} }, | |
{ name="Prometheus", keys={0,-1,0,-1,0,-1,0,-1,1,0,0,-1} }, | |
{ name="Tritone", keys={0,0,-1,1,0,-1,0,0,-1,1,0,-1} }, | |
{ name="Harmonic Major", keys={0,-1,0,-1,0,0,-1,0,0,-1,1,0} }, | |
{ name="Harmonic Minor", keys={0,-1,0,0,-1,0,-1,0,0,-1,1,0} }, | |
{ name="Melodic Minor", keys={0,-1,0,0,-1,0,-1,0,-1,0,-1,0} }, | |
{ name="All Minor", keys={0,-1,0,0,-1,0,-1,0,0,0,0,0} }, | |
{ name="Dorian", keys={0,-1,0,0,-1,0,-1,0,-1,0,0,-1} }, | |
{ name="Phrygian", keys={0,0,-1,0,-1,0,-1,0,0,-1,0,-1} }, | |
{ name="Phrygian Dominant", keys={0,0,-1,-1,0,0,-1,0,0,-1,0,-1} }, | |
{ name="Lydian", keys={0,-1,0,-1,0,-1,0,0,-1,0,-1,0} }, | |
{ name="Lydian Augmented", keys={0,-1,0,-1,0,-1,0,-1,0,0,-1,0} }, | |
{ name="Mixolydian", keys={0,-1,0,-1,0,0,-1,0,-1,0,0,-1} }, | |
{ name="Locrian", keys={0,0,-1,0,-1,0,0,-1,0,-1,0,-1} }, | |
{ name="Locrian Major", keys={0,-1,0,-1,0,0,0,-1,0,-1,0,-1} }, | |
{ name="Super Locrian", keys={0,0,-1,0,0,-1,0,-1,0,-1,0,-1} }, | |
{ name="Neapolitan Major", keys={0,0,-1,0,-1,0,-1,0,-1,0,-1,0} }, | |
{ name="Neapolitan Minor", keys={0,0,-1,0,-1,0,-1,0,0,-1,-1,0} }, | |
{ name="Romanian Minor", keys={0,-1,0,0,-1,1,0,0,-1,0,0,-1} }, | |
{ name="Spanish Gypsy", keys={0,0,-1,1,0,0,-1,0,0,-1,1,0} }, | |
{ name="Hungarian Gypsy", keys={0,-1,0,0,-1,1,0,0,0,-1,1,0} }, | |
{ name="Enigmatic", keys={0,0,-1,1,0,-1,0,-1,0,-1,0,0} }, | |
{ name="Overtone", keys={0,-1,0,-1,0,-1,0,0,-1,0,0,-1} }, | |
{ name="Diminished Half", keys={0,0,-1,0,0,-1,0,0,-1,0,0,-1} }, | |
{ name="Diminished Whole", keys={0,-1,0,0,-1,0,0,-1,0,0,-1,0} }, | |
{ name="Spanish Eight-Tone", keys={0,0,-1,0,0,0,0,-1,0,-1,0,-1} }, | |
{ name="Nine-Tone Scale", keys={0,-1,0,0,0,-1,0,0,0,0,-1,0} } | |
} | |
-- keys list | |
local KEYS = { | |
"C", "C#", "D", "D#", | |
"E", "F", "F#", "G", | |
"G#", "A", "A#", "B" | |
} | |
local CHORDS = { | |
{ -- major | |
{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11} | |
}, | |
{ -- major | |
{0,4},{1,5},{2,6},{3,7},{4,8},{5,9},{6,10},{7,11},{8,0},{9,1},{10,2},{11,3} | |
}, | |
{ -- minor | |
{0,3},{1,4},{2,5},{3,6},{4,7},{5,8},{6,9},{7,10},{8,11},{9,0},{10,1},{11,2} | |
}, | |
{ -- major | |
{0,4,7},{1,5,8},{2,6,9},{3,7,10},{4,8,11},{5,9,0},{6,10,1},{7,11,2},{8,0,3},{9,1,4},{10,2,5},{11,3,6} | |
}, | |
{ -- minor | |
{0,3,7},{1,4,8},{2,5,9},{3,6,10},{4,7,11},{5,8,0},{6,9,1},{7,10,2},{8,11,3},{9,0,4},{10,1,5},{11,2,6} | |
}, | |
{ -- sus4 | |
{0,5,7},{1,6,8},{2,7,9},{3,8,10},{4,9,11},{5,10,0},{6,11,1},{7,0,2},{8,1,3},{9,2,4},{10,3,5},{11,4,6} | |
}, | |
{ -- add9 | |
{0,4,7,2},{1,5,8,3},{2,6,9,4},{3,7,10,5},{4,8,11,6},{5,9,0,7},{6,10,1,8},{7,11,2,9},{8,0,3,10},{9,1,4,11},{10,2,5,0},{11,3,6,1} | |
}, | |
{ -- madd9 | |
{0,3,7,2},{1,4,8,3},{2,5,9,4},{3,6,10,5},{4,7,11,6},{5,8,0,7},{6,9,1,8},{7,10,2,9},{8,11,3,10},{9,0,4,11},{10,1,5,0},{11,2,6,1} | |
}, | |
{ -- 6 | |
{0,4,7,9},{1,5,8,10},{2,6,9,11},{3,7,10,0},{4,8,11,1},{5,9,0,2},{6,10,1,3},{7,11,2,4},{8,0,3,5},{9,1,4,6},{10,2,5,7},{11,3,6,8} | |
}, | |
{ -- m6 | |
{0,3,7,9},{1,4,8,10},{2,5,9,11},{3,6,10,0},{4,7,11,1},{5,8,0,2},{6,9,1,3},{7,10,2,4},{8,11,3,5},{9,0,4,6},{10,1,5,7},{11,2,6,8} | |
}, | |
{ -- 7 | |
{0,4,7,10},{1,5,8,11},{2,6,9,0},{3,7,10,1},{4,8,11,2},{5,9,0,3},{6,10,1,4},{7,11,2,5},{8,0,3,6},{9,1,4,7},{10,2,5,8},{11,3,6,9} | |
}, | |
{ -- m7 | |
{0,3,7,10},{1,4,8,11},{2,5,9,0},{3,6,10,1},{4,7,11,2},{5,8,0,3},{6,9,1,4},{7,10,2,5},{8,11,3,6},{9,0,4,7},{10,1,5,8},{11,2,6,9} | |
}, | |
{ -- 69 | |
{0,2,4,9},{1,3,5,10},{2,4,6,11},{3,5,7,0},{4,6,8,1},{5,7,9,2},{6,8,10,3},{7,9,11,4},{8,10,0,5},{9,11,1,6},{10,0,2,7},{11,1,3,8} | |
}, | |
} | |
local REC_MAJ_1 = 1 | |
local REC_MAJ_2 = 2 | |
local REC_MIN_2 = 3 | |
local REC_MAJ_3 = 4 | |
local REC_MIN_3 = 5 | |
local REC_SUS4 = 6 | |
local REC_ADD9 = 7 | |
local REC_MADD9 = 8 | |
local REC_6 = 9 | |
local REC_M6 = 10 | |
local REC_7 = 11 | |
local REC_M7 = 12 | |
local REC_69 = 13 | |
local CHORD_MAJOR = 1 | |
local CHORD_MINOR = 2 | |
local CHORD_SUS4 = 3 | |
local CHORD_ADD9 = 4 | |
local CHORD_MADD9 = 5 | |
local CHORD_6 = 6 | |
local CHORD_M6 = 7 | |
local CHORD_7 = 8 | |
local CHORD_M7 = 9 | |
local CHORD_69 = 10 | |
local MAX_CONTROL_NOTE = 23 | |
local NOTE_C = 0 | |
local NOTE_E = 4 | |
local NOTE_G = 7 | |
local NOTE_B = 11 | |
local CHANNEL_DEFAULT = 1 | |
-- output channel | |
local selected_channel = CHANNEL_DEFAULT | |
-- autoacc | |
local selected_chord_mode = CHORD_MODE_DEFAULT | |
local selected_chord_key = NOTE_C | |
local selected_chord_key_fixed = NOTE_C | |
local selected_chord_key_old = NOTE_C | |
local selected_chord_scale = CHORD_MAJOR | |
local selected_chord_scale_old = CHORD_MAJOR | |
local selected_on_new_chord = ON_NEW_CHORD_DEFAULT | |
local selected_near_note_mode = NEAR_NOTE_MODE_DEFAULT | |
-- scaler | |
local selected_key = 1 | |
local selected_scale = 1 | |
-- transposer | |
local selected_octave = 0 | |
local selected_transpose = 0 | |
-- tables | |
local realNotes = {} | |
local blockEvents = {} | |
local controlNotes = {} | |
--[[ SCALER ]]-- | |
-- create a scale list for use in parameter list | |
function tidy_scale_list() | |
local scale_list = { } | |
for k, v in ipairs(SCALES) do | |
scale_list[k] = v.name | |
end | |
return scale_list | |
end | |
-- remap a note value to the selected scale and key | |
function restrict_to_scale(note_value) | |
note_value = note_value - selected_key | |
return note_value + SCALES[selected_scale].keys[note_value % 12 + 1] + selected_key | |
end | |
--[[ AUTOACC ]]-- | |
function findNewNote(real) | |
for k, v in ipairs(realNotes) do | |
if v.real == real then | |
new = v.new | |
table.remove(realNotes, k) | |
return new | |
end | |
end | |
end | |
function findChord(types, notes, total, chords) | |
for chNum, ch in pairs(types) do | |
for keyNum, nt in pairs(CHORDS[ch]) do | |
local count = 0 | |
for _, nte in pairs(nt) do | |
if notes[nte] then | |
count = count + 1 | |
end | |
end | |
if count == total then | |
selected_chord_key_old = selected_chord_key | |
selected_chord_key = keyNum - 1 -- keyNum is 1 based | |
selected_chord_key_fixed = selected_chord_key | |
if selected_chord_key > 6 then | |
selected_chord_key_fixed = selected_chord_key - 12 | |
end | |
selected_chord_scale_old = selected_chord_scale | |
selected_chord_scale = chords[chNum] | |
return true | |
end | |
end | |
end | |
end | |
function recognizeChord() | |
local notes = {} | |
local size = 0 | |
for n, v in pairs(controlNotes) do | |
local noteNum = n % 12 | |
if v and notes[noteNum] ~= true then | |
notes[noteNum] = true | |
size = size + 1 | |
end | |
end | |
if size >= 4 and findChord({ | |
REC_MADD9, REC_ADD9, REC_6, REC_M6, REC_7, REC_M7, REC_69 | |
}, notes, 4, { | |
CHORD_MADD9, CHORD_ADD9, CHORD_6, CHORD_M6, CHORD_7, CHORD_M7, CHORD_69 | |
}) then return true end | |
if size == 3 and findChord({REC_SUS4, REC_MIN_3, REC_MAJ_3}, notes, 3, {CHORD_SUS4, CHORD_MINOR, CHORD_MAJOR}) then return true end | |
if size == 2 and findChord({REC_MIN_2, REC_MAJ_2}, notes, 2, {CHORD_MINOR, CHORD_MAJOR}) then return true end | |
if size == 1 and findChord({REC_MAJ_1}, notes, 1, {CHORD_MAJOR}) then return true end | |
end | |
function applyChord(note_value) | |
local noteNum = note_value % 12 | |
local chord_mode = selected_chord_mode | |
local newNoteValue = -1 | |
if selected_chord_mode == CHORD_MODE.RANDOM then | |
local mode = math.random(1, tLen(CHORD_MODE) - 1) | |
if mode == 1 then | |
chord_mode = CHORD_MODE.DEFAULT | |
elseif mode == 2 then | |
chord_mode = CHORD_MODE.BASS | |
end | |
end | |
if noteNum == NOTE_E then | |
if selected_chord_scale == CHORD_MINOR or selected_chord_scale == CHORD_MADD9 then | |
newNoteValue = note_value + selected_chord_key_fixed - 1 | |
elseif selected_chord_scale == CHORD_SUS4 then | |
newNoteValue = note_value + selected_chord_key_fixed + 1 | |
end | |
elseif noteNum == NOTE_B then | |
if (selected_chord_scale == CHORD_ADD9 or selected_chord_scale == CHORD_MADD9) and chord_mode == CHORD_MODE.DEFAULT then | |
newNoteValue = note_value + selected_chord_key_fixed + 3 | |
elseif (selected_chord_scale == CHORD_7 or selected_chord_scale == CHORD_M7) and chord_mode == CHORD_MODE.DEFAULT then | |
newNoteValue = note_value + selected_chord_key_fixed - 1 | |
elseif selected_chord_scale == CHORD_69 and chord_mode == CHORD_MODE.DEFAULT then | |
newNoteValue = note_value + selected_chord_key_fixed + 3 | |
else | |
newNoteValue = note_value + selected_chord_key_fixed + 1 | |
end | |
elseif noteNum == NOTE_C then | |
if (selected_chord_scale == CHORD_ADD9 or selected_chord_scale == CHORD_MADD9) and chord_mode == CHORD_MODE.DEFAULT then | |
newNoteValue = note_value + selected_chord_key_fixed + 2 | |
elseif (selected_chord_scale == CHORD_7 or selected_chord_scale == CHORD_M7) and chord_mode == CHORD_MODE.DEFAULT then | |
newNoteValue = note_value + selected_chord_key_fixed - 2 | |
elseif selected_chord_scale == CHORD_69 and chord_mode == CHORD_MODE.DEFAULT then | |
newNoteValue = note_value + selected_chord_key_fixed + 2 | |
end | |
elseif noteNum == NOTE_G then | |
if selected_chord_scale == CHORD_6 or selected_chord_scale == CHORD_M6 then | |
newNoteValue = note_value + selected_chord_key_fixed + 2 | |
elseif selected_chord_scale == CHORD_69 then | |
newNoteValue = note_value + selected_chord_key_fixed + 2 | |
end | |
end | |
if newNoteValue == -1 then | |
newNoteValue = note_value + selected_chord_key_fixed | |
end | |
-- near note fix | |
if selected_near_note_mode == NEAR_NOTE_MODE.ON then | |
local rootC = note_value - noteNum | |
if newNoteValue > rootC + 7 then | |
newNoteValue = newNoteValue - 12 | |
elseif newNoteValue < rootC - 7 then | |
newNoteValue = newNoteValue + 12 | |
end | |
end | |
return newNoteValue | |
end | |
-- main block processing | |
function plugin.processBlock(samples, smax, midiBuf) | |
if selected_chord_mode ~= CHORD_MODE.BYPASS then | |
math.randomseed(os.time()) | |
needToRecalcChord = false | |
end | |
blockEvents = {} | |
for ev in midiBuf:eachEvent() do | |
if ev:isNoteOn() then | |
real = ev:getNote() | |
if selected_chord_mode == CHORD_MODE.BYPASS or real > MAX_CONTROL_NOTE then | |
table.insert(blockEvents, midi.Event(ev)) | |
else | |
controlNotes[real] = true | |
needToRecalcChord = true | |
end | |
elseif ev:isNoteOff() then | |
real = ev:getNote() | |
if selected_chord_mode == CHORD_MODE.BYPASS or real > MAX_CONTROL_NOTE then | |
table.insert(blockEvents, midi.Event(ev)) | |
else | |
controlNotes[real] = false | |
needToRecalcChord = true | |
end | |
end | |
end | |
if selected_chord_mode ~= CHORD_MODE.BYPASS and needToRecalcChord == true then | |
if recognizeChord() == true and (selected_chord_key ~= selected_chord_key_old or selected_chord_scale ~= selected_chord_scale_old) then | |
-- split long note on new chord | |
if selected_on_new_chord == ON_NEW_CHORD.SPLIT then | |
for k, v in pairs(realNotes) do | |
table.insert(blockEvents, 1, midi.Event.noteOn(v.channelReal, v.real, v.vel)) | |
table.insert(blockEvents, 1, midi.Event.noteOff(v.channelNew, v.new)) | |
end | |
realNotes = {} | |
-- stop long note on new chord | |
elseif selected_on_new_chord == ON_NEW_CHORD.STOP then | |
for k, v in pairs(realNotes) do | |
table.insert(blockEvents, 1, midi.Event.noteOff(v.channelNew, v.new)) | |
end | |
realNotes = {} | |
end | |
end | |
end | |
midiBuf:clear() | |
if #blockEvents > 0 then | |
for _, ev in ipairs(blockEvents) do | |
local real = ev:getNote() | |
local new = real | |
local needTransform = selected_octave ~= 0 or | |
selected_transpose ~= 0 or | |
selected_chord_mode ~= CHORD_MODE.BYPASS or | |
selected_scale ~= 1 | |
if ev:isNoteOn() then | |
if needTransform then | |
-- autoacc | |
if selected_chord_mode ~= CHORD_MODE.BYPASS then | |
new = applyChord(real) | |
end | |
-- scaler | |
if selected_scale ~= 1 then | |
new = restrict_to_scale(new) | |
end | |
-- octave shift | |
if selected_octave ~= 0 then | |
new = math.min(math.max(MIN_NOTE, new + selected_octave * 12), MAX_NOTE) | |
end | |
-- semitone transpose | |
if selected_transpose ~= 0 then | |
new = math.min(math.max(MIN_NOTE, new + selected_transpose), MAX_NOTE) | |
end | |
channelReal = ev:getChannel() | |
table.insert(realNotes, { | |
new=new, | |
real=real, | |
channelNew=selected_channel, | |
channelReal=channelReal, | |
vel=ev:getVel() | |
}) | |
end | |
ev:setNote(new) | |
-- output channel | |
ev:setChannel(selected_channel) | |
elseif ev:isNoteOff() then | |
if needTransform then | |
new = findNewNote(real) | |
end | |
if new then | |
ev:setChannel(selected_channel) | |
ev:setNote(new) | |
end | |
end | |
midiBuf:addEvent(ev) | |
end | |
end | |
end | |
params = plugin.manageParams { | |
-- output MIDI channel | |
{ | |
name = "Output channel"; | |
type = "list"; | |
values = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; | |
default = CHANNEL_DEFAULT; | |
changed = function(val) selected_channel = val end; | |
}; | |
-- NTT | |
{ | |
name = "Chord mode"; | |
type = "list"; | |
values = {CHORD_MODE.DEFAULT, CHORD_MODE.BASS, CHORD_MODE.RANDOM, CHORD_MODE.BYPASS}; | |
default = CHORD_MODE_DEFAULT; | |
changed = function(val) selected_chord_mode = val end; | |
}; | |
-- Ignored when selected_chord_mode == CHORD_MODE.BYPASS | |
{ | |
name = "On new chord"; | |
type = "list"; | |
values = {ON_NEW_CHORD.SPLIT, ON_NEW_CHORD.STOP, ON_NEW_CHORD.DONOTHING}; | |
default = ON_NEW_CHORD_DEFAULT; | |
changed = function(val) selected_on_new_chord = val end; | |
}; | |
-- Fix note to nearest in template. Ignored when selected_chord_mode == CHORD_MODE.BYPASS | |
{ | |
name = "Near note"; | |
type = "list"; | |
values = {NEAR_NOTE_MODE.OFF, NEAR_NOTE_MODE.ON}; | |
default = NEAR_NOTE_MODE_DEFAULT; | |
changed = function(val) selected_near_note_mode = val end; | |
}; | |
-- Scaler | |
{ | |
name = "Scaler key", | |
type = "list", | |
values = KEYS, | |
default = 1, | |
changed = function(val) selected_key = table.find(KEYS, val) - 1 end | |
}, | |
{ | |
name = "Scaler scale", | |
type = "list", | |
values = tidy_scale_list(), | |
default = 1, | |
changed = function(val) selected_scale = table.find(tidy_scale_list(), val) end | |
}, | |
-- Octave shift | |
{ | |
name = "Octave shift", | |
type = "list", | |
values = { | |
-11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1, | |
0, | |
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 | |
}, | |
default = 0, | |
changed = function(val) selected_octave = val end | |
}, | |
-- Semitone shift | |
{ | |
name = "Transpose", | |
type = "list", | |
values = { | |
-11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1, | |
0, | |
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 | |
}, | |
default = 0, | |
changed = function(val) selected_transpose = val end | |
}, | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment