A Reaper script for switching guitar lessons

  1. Start
  2. The script
  3. Better
  4. Even better

I play guitar daily. Each time I need to repeat a set of exercises at a specific tempo, which is boring by itself, but even more boring is switching between them.

§ Start

At first, I played all tasks one by one, creating a track per day. However, this wasn’t very helpful as different tasks were getting better at a different pace. Moreover, I needed to split each file in order to get the bounds of different exercises.

|2022-01-01| exercise1 exercise2
|2022-01-02| exercise1 exercise3

Then I came up with another approach – each track became an exercise. Reaper adds a timestamp at every recording so it was easy to keep track of everything given meaningful names for tracks. I still needed to keep tempo in mind, so at some point I just appended it to every item, resulting in track names like “ladder 120” or “1-2-3-4 95”.

|exercise1 90| exercise_1_2022-01-01.mp3 exercise_2_2022-01-02.mp3
|exercise2 120| exercise_1_2022-01-01.mp3 exercise_2_2022-01-02.mp3

Another small but nifty optimisation was moving all guitar effects to a master FX chain. Before that, I had a parent folder which made all other tracks indented which didn’t look cool.

§ The script

I realised that on every exercise switch I did repetitive work:

  • Unarming previous track, arming the current one
  • Unsoloing previous track, soloing the current one
  • Adjusting the tempo

This made a good candidate for automation. As usual, script appendix is just capturing the action as a block (r is an alias for reaper):

r.PreventUIRefresh(1)
r.Undo_BeginBlock2(0)
main()
r.Undo_EndBlock2(0, "Next lesson", 16)
r.UpdateArrange()
r.PreventUIRefresh(-1)

Then there comes the arm part. The easiest way is just checking every track whether it’s armed. If it is, we should switch to the next one:

-- local function main()
local tracks_num = r.CountTracks(0)
if tracks_num == 0 then return end
local track, idx = nil, -1

for i = 0, tracks_num - 1 do
    track = r.GetTrack(0, i)
    if r.GetMediaTrackInfo_Value(track, "I_RECARM") == 1 then
        idx = i
        break
    end
end

if idx == -1 then return switch(true, r.GetTrack(0, 0)) end
switch(false, track)
switch(true, r.GetTrack(0, (idx + 1) % tracks_num))

Caveat – this won’t work if the project is in invalid state e.g. many tracks armed at once, but we can solve this by simply not getting it to an invalid state.

The last part is the switch itself:

-- local function switch(on, track)
r.SetMediaTrackInfo_Value(track, "I_SOLO", on and 1 or 0)
r.SetMediaTrackInfo_Value(track, "I_RECARM", on and 1 or 0)
if not on then return end

local _, name = r.GetTrackName(track)
local bpm = name:match "(%d+)$"
if bpm then r.SetCurrentBPM(0, bpm, false) end

Here we parse last number in track name and adjust the tempo.

§ Better

What’s more that we can automate? If there are many days recorded for every exercise, you have to adjust the end cursor manually as exercises are likely to have different lengths. Actually, Reaper allows you to mitigate that:

-- in the switch function
local items_num, cursor = r.CountTrackMediaItems(track), 0
if items_num ~= 0 then
    local last = r.GetTrackMediaItem(track, items_num - 1)
    cursor = r.GetMediaItemInfo_Value(last, "D_POSITION")
      + r.GetMediaItemInfo_Value(last, "D_LENGTH")
end
r.SetEditCurPos(cursor, true, true)

Cursor should be positioned at end which is last item’s start plus its length.

§ Even better

We can’t get any better, can we? Actually, we can! Each time we switch the exercise, we have to re-run the script manually. Of course, you can bind it to a hotkey, but having a single one dedicated to a task that’s rather rare doesn’t seem like the best solution.

Fortunately, there’s “Action: repeat the most recent action” (id 2999). You can bind this instead and just press the hotkey for every switch except for the first one.

I use reaper-keys in which there’s a . (dot) action. It repeats the last task done via reaper-keys itself but you can use <C+.> (Ctrl + dot) to repeat the last action.

Script source is available on Sourcehut.