Add diatonic chord entry and velocity control
Chords are built by stacking scale thirds rather than from a quality table, so the chord follows the degree automatically -- in C major, Alt+3 on C gives C major, on D gives D minor, on B gives B diminished. Toggling delegates to toggleCell, so chord tones inherit hold-mode joining, splitting and trimming unchanged. Velocity keys act on the note under the cursor if there is one, and on the default for new notes otherwise, announcing which. That avoids a mode switch between setting a level to draw at and shaping dynamics after the fact. Also fixes install.ps1 failing to resolve its own directory: $PSScriptRoot is not reliably populated during parameter binding, so it is resolved in the body. Tests cover chord quality per scale degree and range clamping.
This commit is contained in:
@@ -352,6 +352,70 @@ function M.toggleCell(take, pitch)
|
||||
return result, detail
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------- chords
|
||||
|
||||
-- Stack `count` diatonic chord tones on `pitch`, skipping a scale degree
|
||||
-- between each -- so in a major scale, degree 1 gives a major triad and
|
||||
-- degree 2 a minor one, exactly as harmony expects. In the chromatic scale
|
||||
-- this degenerates to stacked whole tones, which is arguably useless but at
|
||||
-- least predictable.
|
||||
function M.chordPitches(pitch, count)
|
||||
local ps = { pitch }
|
||||
local p = pitch
|
||||
for _ = 2, count do
|
||||
local a = M.scaleStep(p, 1); if not a then return nil end
|
||||
local b = M.scaleStep(a, 1); if not b then return nil end
|
||||
p = b
|
||||
ps[#ps + 1] = p
|
||||
end
|
||||
return ps
|
||||
end
|
||||
|
||||
-- Toggle a whole chord in the current cell. If every tone is already
|
||||
-- present the chord is removed; otherwise the missing tones are filled in.
|
||||
-- Delegates to toggleCell so hold mode, splitting and trimming all behave
|
||||
-- identically to single notes.
|
||||
function M.toggleChord(take, pitches)
|
||||
local ppqA, ppqB = M.cellPPQ(take)
|
||||
local allPresent = true
|
||||
for _, p in ipairs(pitches) do
|
||||
if not M.noteInCell(take, p, ppqA, ppqB) then allPresent = false break end
|
||||
end
|
||||
|
||||
reaper.Undo_BeginBlock2(0)
|
||||
local changed = {}
|
||||
for _, p in ipairs(pitches) do
|
||||
local here = M.noteInCell(take, p, ppqA, ppqB) ~= nil
|
||||
if allPresent or not here then
|
||||
M.toggleCell(take, p)
|
||||
changed[#changed + 1] = p
|
||||
end
|
||||
end
|
||||
reaper.Undo_EndBlock2(0, "MIDI Grid: toggle chord", -1)
|
||||
return (allPresent and "off" or "on"), changed
|
||||
end
|
||||
|
||||
------------------------------------------------------------------ velocity
|
||||
|
||||
-- Adjust velocity by delta. If a note sits at the cursor cell and pitch, its
|
||||
-- velocity changes; otherwise the default for newly inserted notes does.
|
||||
-- Returns "note" or "default", and the resulting value.
|
||||
function M.nudgeVelocity(take, pitch, delta)
|
||||
local ppqA, ppqB = M.cellPPQ(take)
|
||||
local idx, _, _, _, vel = M.noteInCell(take, pitch, ppqA, ppqB)
|
||||
if idx then
|
||||
local v = math.max(1, math.min(127, math.floor(vel + delta)))
|
||||
reaper.Undo_BeginBlock2(0)
|
||||
reaper.MIDI_SetNote(take, idx, nil, nil, nil, nil, nil, nil, v, true)
|
||||
reaper.MIDI_Sort(take)
|
||||
reaper.Undo_EndBlock2(0, "MIDI Grid: set velocity", -1)
|
||||
return "note", v
|
||||
end
|
||||
local v = math.max(1, math.min(127, math.floor(M.getNum("vel", 96) + delta)))
|
||||
M.set("vel", v)
|
||||
return "default", v
|
||||
end
|
||||
|
||||
------------------------------------------------------------------ audition
|
||||
|
||||
--[[
|
||||
|
||||
Reference in New Issue
Block a user