A Reaper script for switching guitar lessons
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.