CAL | AZ Lua | |
Not MIDI functionality | YES | NO |
Multi-track functionality | YES | NO |
"Look back" and "multi-pass" MIDI processing | YES | NO |
Offline MIDI processing | YES | YES |
Live MIDI processing | NO | YES |
Non destructive MIDI processing | NO | YES |
function OnInput(pqIn, pqOut)
for i = 1, #pqIn do
e = pqIn[i]
if e.Key and (e.Key % 12 == 0) then
e = nil
end
pqOut.add(e)
end
end
OnEvents = function ( From, To, pqIn, pqOut)
OnInput(pqIn, pqOut)
end
For Lua experts: I know there is better way for looping, "e" should be local and the condition can be reversed to make the code shorter ;)--[[ The is my dummy preset ]]--
The text will be displayed in the User mode, so everyone can get an idea what that preset is doing without looking into the "code".OnEvents = function ( From, To, pqIn, pqOut)
OnInput(pqIn, pqOut)
end
and then you need only one function, OnInput.function OnInput(pqIn, pqOut)
for i = 1, #pqIn do
local e = pqIn[i]
-- Do something with "e"
pqOut.add(e)
end
end
function OnInput(pqIn, pqOut)
for i,e in ipairs(pqIn) do
-- Do something with "e"
pqOut.add(e)
end
end
In that exact form, your MFX will just copy all events from the input table into the output table. Effectively the same "functionality" as not existing or disabled MFX plug-in. As I have already mentioned, defining the function but leaving it empty will BLOCK all MIDI events. So, you already know 2 "extreme" MFX plug-in definitions :o--[[Transpose, optionally with octave note duplication ]]-
-- GUI definition part --
local Transpose = { Label = "Transpose", Type = "Int", Value = 0, Min = -48, Max = 48 }
local Octave = { Label = "Octave", Type = "Bool", Value = false }
GUI = { Transpose, Octave }
-- Processing part --
local active = MfxOffNotes.new()
function OnInput(pqIn, pqOut)
for i,e in ipairs(pqIn) do
e = active.move(e, pqOut )
if not e then
elseif e.Vel then
base = e:copy()
e.Key = e.Key + Transpose.Value
active.add(base, e)
if Octave.Value then
pqOut.add(e)
e.Key = e.Key + 12
active.add(base, e)
end
end
pqOut.add(e)
end
end
OnEvents = function ( From, To, pqIn, pqOut)
OnInput(pqIn, pqOut)
end
local active = MfxOffNotes.new()
function OnInput(pqIn, pqOut)
for i,e in ipairs(pqIn) do
e = active.move(e, pqOut )
if not e then
elseif e.Vel then
base = e:copy()
-- here you conditionally modify e
active.add(base, e)
--
-- to send more then one note, you can repeat:
-- pqOut.add(e)
-- e.Key = ... (new modification)
-- active.add(base,e)
end
pqOut.add(e)
end
end
OnEvents = function ( From, To, pqIn, pqOut)
OnInput(pqIn, pqOut)
end
--[[ Split to channel 1 / 2 by key 64 ]]--
function OnInput(pqIn, pqOut)
for i,e in ipairs(pqIn) do
if e.Key then
if e.Key < 64 then
e.Chan = 1
else
e.Chan = 2
end
end
pqOut.add(e)
end
end
OnEvents = function ( From, To, pqIn, pqOut)
OnInput(pqIn, pqOut)
end
--[[ Convert modulation to the Program Change ]]--
function OnInput(pqIn, pqOut)
for i,e in ipairs(pqIn) do
if e.Type == Control and e.Num == 1 then
local pc = MfxEvent.new(Patch)
pc.Time = e.Time
pc.Chan = e.Chan
pc.Patch = e.Val
e = pc
end
pqOut.add(e)
end
end
OnEvents = function ( From, To, pqIn, pqOut)
OnInput(pqIn, pqOut)
end
--[[Use C3 as a Switch Key to transpose one octave ]]--
local switch_key = MfxKey("C3")
--
local active = MfxOffNotes.new()
local skey_live = false
local skey_clip = { from = 0, to = 0 }
function SwitchKey( e )
if not e or not e.Key or (e.Key ~= switch_key) then
return e
end
if e.Type == Note then
skey_clip.from = e.Time
skey_clip.to = e.Time + e.Duration
elseif e.Type == NoteOn then
skey_live = true
else -- NoteOff
skey_live = false
end
return nil
end
function IsSwitchedAt( time )
return skey_live or
( (time >= skey_clip.from) and (time < skey_clip.to) )
end
function OnInput(pqIn, pqOut)
for i,e in ipairs(pqIn) do
e = active.move(e, pqOut )
e = SwitchKey(e)
if not e then
elseif e.Vel then -- process Note/NoteOn (but not NoteOff)
base = e:copy()
if IsSwitchedAt(e.Time) then
e.Key = e.Key + 12 -- transpose
end
active.add(base, e)
end
pqOut.add(e)
end
end
OnEvents = function ( From, To, pqIn, pqOut)
OnInput(pqIn, pqOut)
end
--[[
3 band channel splitter, middle band is between Bass and Voice
Each band has own velocity control and can be transposed.
Bands assigned to MIDI channel 1,2 and 3 on output. AZ, 2016
]]--
local Band = {}
local BassKey = { Label = "Bass under", Type = "Key", Value = MfxKey("C3") }
local VoiceKey = { Label = "Voice over", Type = "Key", Value = MfxKey("C5") }
local Solo = { Label = "Solo band (0 - no solo)", Type = "Int", Value = 0, Min = 0, Max = 3 }
local band_names = { "Bass", "Middle", "Voice"}
GUI = {
BassKey, VoiceKey, Solo
}
for i, name in ipairs( band_names ) do
local b = {}
b.Transpose = { Label = name.." transpose", Type = "Int", Value = 0, Min = -48, Max = 48 }
b.Velocity = { Label = name.." velocity", Type = "Int", Value = 0, Min = -48, Max = 48 }
Band[i] = b
table.insert(GUI, b.Transpose)
table.insert(GUI, b.Velocity)
end
local active = MfxOffNotes.new()
function OnInput(pqIn, pqOut)
for i,e in ipairs(pqIn) do
e = active.move(e, pqOut )
if not e then
elseif e.Vel then
base = e:copy()
local i = 2 -- Middle
if e.Key < BassKey.Value then
i = 1
elseif e.Key > VoiceKey.Value then
i = 3
end
if Solo.Value > 0 and Solo.Value ~= i then
e = nil
else
e.Chan = i
e.Key = e.Key + Band[i].Transpose.Value
e.Vel = e.Vel + Band[i].Velocity.Value
end
active.add(base, e)
end
pqOut.add(e)
end
end
OnEvents = function ( From, To, pqIn, pqOut)
OnInput(pqIn, pqOut)
end
--[[
Use Controller to switch MIDI channel for Notes
AZ 2016
]]--
local Ctl = { Label = "Controller", Type = "Int", Value = 1 }
local ChMax = { Label = "Max channel", Type = "Int", Value = 4, Min = 2, Max = 16 }
GUI = { Ctl, ChMax }
--
local active = MfxOffNotes.new()
local Channel = 0
local floor = math.floor
function SwitchCC( e )
if not e or not e.Num or (e.Num ~= Ctl.Value) then
return e
end
Channel = floor( e.Val * ChMax.Value / 128 )
return nil
end
function OnInput(pqIn, pqOut)
for i,e in ipairs(pqIn) do
e = active.move(e, pqOut )
e = SwitchCC(e)
if not e then
elseif e.Vel then
base = e:copy()
e.Chan = Channel + 1
active.add(base, e)
end
pqOut.add(e)
end
end
OnEvents = function ( From, To, pqIn, pqOut)
OnInput(pqIn, pqOut)
end
--[[
OFFLINE PROCESSOR ONLY! Does not work as a filter.
Apply tempo map to free played MIDI.
AZ 2016
]]--
local BPM = { Label = "Original BPM", Type = "Int", Value = 100, Min = 50, Max = 240 }
local PPQ = Mfx.GetTimebase()
local T2M = Mfx.TicksToMsecs
local M2T = Mfx.MsecsToTicks
local round = math.floor
GUI = { BPM }
--
function OnInput(pqIn, pqOut)
-- original converter to Msec
local k = 60000/BPM.Value/PPQ
for i,e in ipairs(pqIn) do
local ebegin = e.Time * k
local eend = (e.Time + e.Duration) * k
e.Time = M2T(round(ebegin))
e.Duration = M2T(round(eend)) - e.Time
pqOut.add(e)
end
end
OnEvents = function ( From, To, pqIn, pqOut)
OnInput(pqIn, pqOut)
end
--[[
Extend some notes to make Portamento works
on Yamaha synth. AZ, 2016
]]--
function OnInput(pqIn, pqOut, To)
local last_on = 0
for i,e in ipairs(pqIn) do
if e.Type == NoteOn then
last_on = e.Time
elseif e.Type == NoteOff then
if e.Time == last_on then
e.Time = e.Time + 1
end
elseif e.Type == Note then
local next_on = To -- we do not know either some note will come after
for ni = i + 1, #pqIn do -- find the first note, when available
ne = pqIn[ni]
if ne.Type == Note then
next_on = ne.Time
break
end
end
if next_on <= e.Time + e.Duration then
e.Duration = e.Duration + 1
end
end
pqOut.add(e)
end
end
OnEvents = function ( From, To, pqIn, pqOut)
OnInput(pqIn, pqOut, To)
end
--[[
Send CC1 and CC7 on playback start
Use Controller to switch MIDI channel for Notes
AZ 2016
]]--
local CC1 = { Label = "CC1", Type = "Int", Value = 64 }
local CC7 = { Label = "CC7", Type = "Int", Value = 64 }
GUI = { CC1, CC7 }
--
local cc17sent = false
function OnStart( )
cc17sent = false
end
function OnInput(pqIn, pqOut)
for i,e in ipairs(pqIn) do
pqOut.add(e)
end
end
function OutCC(pqOut, time, num, value)
local e = MfxEvent.new( Control )
e.Time = time
e.Num = num
e.Val = value
pqOut.add(e)
end
OnEvents = function ( From, To, pqIn, pqOut)
if not cc17sent then
OutCC(pqOut, From, 1, CC1.Value)
OutCC(pqOut, From, 7, CC7.Value)
cc17sent = true
end
OnInput(pqIn, pqOut)
end
--[[
Drum Map like functionality,
edit the text to set your mapping
AZ, 2016
]]--
-- This table defines the mapping
local dm = {
["E4"] = "D#4", ["A4"] = "G#4", ["B4"] = "A#4"
}
-- The rest you can keep unchanged
-- Convert note names to numbers
local ndm = { }
for noteIn,noteOut in pairs(dm) do
ndm[MfxKey(noteIn)] = MfxKey(noteOut)
end
function OnInput(pqIn, pqOut)
for i,e in ipairs(pqIn) do
if e.Key then -- Is Note ?
local newkey = ndm[e.Key]
if newkey then -- Mapping exist ?
e.Key = newkey -- map the note
end
end
pqOut.add(e)
end
end
OnEvents = function ( From, To, pqIn, pqOut)
OnInput(pqIn, pqOut)
end
--[[
Simple harmonizer
AZ, 2017
]]--
local Scale = { Label = "Scale", Type = "Key", Value = 0, Min = 0, Max = 11 }
local IsMajor = { Label = "Major scale", Type = "Bool", Value = false }
local HarmonyType = {
Label = "T-2, T-1, T+1, T+2", Type = "Int", Value = 0, Min = 0, Max = 3
}
local KeepMelody = { Label = "Keep melody", Type = "Bool", Value = true }
GUI = {
Scale, IsMajor, HarmonyType, KeepMelody
}
local floor = math.floor
-- from absolute note n (0 = c0 ) and root for scale ( 0 = c )
-- calculate octave and shift in that octave, so n = root + octave*12 + shift
-- return base = root + octave*12 and shift
function Normal( n, root )
n = n - root
return root + floor( n / 12 )*12, n % 12
end
-- For major , harmony tones in notes are 0, 4, 7
-- For minor , harmony tones in notes are 0, 3, 7
-- For both functions:
-- integer n : absolute note number
-- return integer harmony tone from basic triad
function NextT( n )
local base, shift
base, shift = Normal( n, Scale.Value )
if IsMajor.Value then
if shift < 4 then return base + 4 end
else
if shift < 3 then return base + 3 end
end
if shift < 7 then return base + 7 end
-- 0 in next octave
return base + 12
end
function PreviousT( n )
local base, shift
base, shift = Normal( n, Scale.Value )
if shift > 7 then return base + 7 end
if IsMajor.Value then
if shift > 4 then return base + 4 end
else
if shift > 3 then return base + 3 end
end
if shift > 0 then return base end
-- 7 in previous octave
return base - 5
end
-- Avoid hanging notes if we change parameters during playing
local active = MfxOffNotes.new()
function OnInput(pqIn, pqOut)
for i,e in ipairs(pqIn) do
e = active.move( e, pqOut ) -- process Note release correctly
if e and e.Vel then -- Process Note and Note On only (Off processed by active)
local t = e:copy()
local tKey
if HarmonyType.Value == 0 then
tKey = PreviousT( t.Key ) -- T-1
tKey = PreviousT( tKey ) -- T-2
elseif HarmonyType.Value == 1 then
tKey = PreviousT( t.Key ) -- T-1
elseif HarmonyType.Value == 2 then
tKey = NextT( t.Key ) -- T+1
else -- HarmonyType.Value == 3
tKey = NextT( t.Key ) -- T+1
tKey = NextT( t.Key ) -- T+2
end
if tKey >= 0 and tKey < 128 then -- is harmony inside note range ?
t.Key = tKey
active.add(t, e)
pqOut.add(t)
end
if KeepMelody.Value then
active.add(e, e)
pqOut.add(e)
end
else
pqOut.add(e) -- all other events
end
end
end
OnEvents = function ( From, To, pqIn, pqOut)
OnInput(pqIn, pqOut)
end