Skip to content

Instantly share code, notes, and snippets.

@pointofpresence
Created August 27, 2023 18:40
Show Gist options
  • Save pointofpresence/ba8dce7f8a286b6ab4ae41fbe1dba777 to your computer and use it in GitHub Desktop.
Save pointofpresence/ba8dce7f8a286b6ab4ae41fbe1dba777 to your computer and use it in GitHub Desktop.
protoplug autoacc v17
--[[
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