Keyboard- and speech-driven step entry for the MIDI editor, built for use with OSARA. Arrows walk grid cells and scale degrees, Enter toggles notes, and every position is spoken and auditioned. Uses REAPER's own edit cursor and active_note_row rather than private cursor state, so grid mode and OSARA editing stay in sync. Grid size comes from MIDI_GetGrid(), so existing grid keybindings drive it. When grid mode is off, every bound key forwards to its previous action. Preview timing lives in a background daemon so the action scripts can exit immediately -- a script still alive on the next keypress triggers REAPER's "already running" prompt and breaks key repeat.
71 lines
2.4 KiB
Lua
71 lines
2.4 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")
|
|
|
|
print(fails == 0 and "ALL PASS" or (fails .. " FAILURES"))
|