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.
88 lines
3.3 KiB
Lua
88 lines
3.3 KiB
Lua
-- Stub just enough of the reaper API to exercise the pure logic in the lib.
|
|
local ext = { root = "0", scale = "1" }
|
|
reaper = {
|
|
GetExtState = function(_, k) return ext[k] or "" end,
|
|
SetExtState = function(_, k, v) ext[k] = v end,
|
|
APIExists = function() return false end,
|
|
ShowConsoleMsg = function() end,
|
|
SNM_GetIntConfigVar = function(_, d) return 1 end,
|
|
GetCursorPosition = function() return 0 end,
|
|
TimeMap_GetTimeSigAtTime = function() return 4, 4 end,
|
|
}
|
|
|
|
local G = dofile("D:\\code\\midigrid\\midigrid_lib.lua")
|
|
|
|
local fails = 0
|
|
local function eq(got, want, label)
|
|
if got ~= want then
|
|
print(("FAIL %s: got %s want %s"):format(label, tostring(got), tostring(want)))
|
|
fails = fails + 1
|
|
end
|
|
end
|
|
|
|
-- Note naming: with midioctoffs=1, note 60 reads as C5 (Reaper's default).
|
|
eq(G.noteName(60), "C5", "noteName 60")
|
|
eq(G.noteName(61), "C#5", "noteName 61")
|
|
eq(G.noteName(72), "C6", "noteName 72")
|
|
eq(G.noteName(48), "C4", "noteName 48")
|
|
|
|
-- C major: stepping up from C5 walks the scale, not semitones.
|
|
ext.root, ext.scale = "0", "1"
|
|
local p, seq = 60, {}
|
|
for _ = 1, 7 do p = G.scaleStep(p, 1); seq[#seq + 1] = p end
|
|
eq(table.concat(seq, ","), "62,64,65,67,69,71,72", "C major ascending")
|
|
|
|
p, seq = 60, {}
|
|
for _ = 1, 3 do p = G.scaleStep(p, -1); seq[#seq + 1] = p end
|
|
eq(table.concat(seq, ","), "59,57,55", "C major descending")
|
|
|
|
-- An off-scale note must not trap the cursor: C#5 in C major steps to D5.
|
|
eq(G.scaleStep(61, 1), 62, "off-scale up")
|
|
eq(G.scaleStep(61, -1), 60, "off-scale down")
|
|
|
|
-- Pentatonic skips more than a tone.
|
|
ext.scale = "10" -- major pentatonic
|
|
eq(G.scaleStep(60, 1), 62, "pentatonic C->D")
|
|
eq(G.scaleStep(64, 1), 67, "pentatonic E->G")
|
|
|
|
-- Chromatic behaves like plain semitones.
|
|
ext.scale = "14"
|
|
eq(G.scaleStep(60, 1), 61, "chromatic up")
|
|
|
|
-- Root transposition: A natural minor.
|
|
ext.root, ext.scale = "9", "2"
|
|
eq(G.inScale(69), true, "A in A minor")
|
|
eq(G.inScale(70), false, "A# not in A minor")
|
|
eq(G.scaleName(), "A natural minor", "scale name")
|
|
|
|
-- Range clamping at the extremes of the MIDI range.
|
|
ext.root, ext.scale = "0", "1"
|
|
eq(G.scaleStep(127, 1), nil, "top of range")
|
|
eq(G.scaleStep(0, -1), nil, "bottom of range")
|
|
|
|
-- Grid labels in 4/4: one bar is 4 QN.
|
|
eq(G.gridLabel(4), "1 bar", "grid 1 bar")
|
|
eq(G.gridLabel(8), "2 bars", "grid 2 bars")
|
|
eq(G.gridLabel(1), "1/4", "grid quarter")
|
|
eq(G.gridLabel(0.25), "1/16", "grid sixteenth")
|
|
eq(G.gridLabel(0.5), "1/8", "grid eighth")
|
|
|
|
-- Diatonic chords: stacking scale thirds must yield the right quality for
|
|
-- each degree, without any chord-quality table.
|
|
ext.root, ext.scale = "0", "1" -- C major
|
|
eq(table.concat(G.chordPitches(60, 3), ","), "60,64,67", "C major triad")
|
|
eq(table.concat(G.chordPitches(62, 3), ","), "62,65,69", "D minor triad")
|
|
eq(table.concat(G.chordPitches(71, 3), ","), "71,74,77", "B diminished triad")
|
|
eq(table.concat(G.chordPitches(60, 4), ","), "60,64,67,71", "C major seventh")
|
|
eq(table.concat(G.chordPitches(67, 4), ","), "67,71,74,77", "G dominant seventh")
|
|
|
|
-- A minor triad from its own root.
|
|
ext.root, ext.scale = "9", "2"
|
|
eq(table.concat(G.chordPitches(69, 3), ","), "69,72,76", "A minor triad")
|
|
|
|
-- Running off the top of the MIDI range yields nil rather than a partial chord.
|
|
ext.root, ext.scale = "0", "1"
|
|
eq(G.chordPitches(127, 3), nil, "chord past top of range")
|
|
|
|
print(fails == 0 and "ALL PASS" or (fails .. " FAILURES"))
|