Skip to content

Commit a19b79f

Browse files
authored
Release Quad Cortex MIDI control v2.3.2 (#1711)
- README update with region combination name examples - Add info after setup on how to add a toolbar button for easy access, or re-execute the setup wizard if needed. - Add MIDI output ID validation in the setup wizard to prevent invalid entries. - README update with clearer setup instructions and troubleshooting tips. - Add info after first config on how to add a toolbar button for easy access, or re-execute the setup wizard if needed. - Add setup to list action list (for easier access and reconfiguration) - Fix folders to fix category detection with reapack-index - Split from original Quad Cortex MIDI Control script to create a more modular and maintainable codebase. - Added a setup wizard for first-time configuration. - Improved logging and error handling. - Automatically creates the MIDI output track if it doesn't exist.
1 parent a4c2d46 commit a19b79f

3 files changed

Lines changed: 368 additions & 144 deletions

File tree

Lines changed: 99 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -1,175 +1,130 @@
11
-- @description Quad Cortex MIDI control
22
-- @author Bertrand C
3-
-- @version 1.0
4-
-- @changelog Initial release
3+
-- @version 2.3.2
4+
-- @changelog
5+
-- - Fix reapack-index linked to wrong git commit
6+
-- - README update with region combination name examples
7+
-- - Add info after setup on how to add a toolbar button for easy access, or re-execute the setup wizard if needed.
8+
-- - Add MIDI output ID validation in the setup wizard to prevent invalid entries.
9+
-- - README update with clearer setup instructions and troubleshooting tips.
10+
-- - Add info after first config on how to add a toolbar button for easy access, or re-execute the setup wizard if needed.
11+
-- - Add setup to list action list (for easier access and reconfiguration)
12+
-- - Fix folders to fix category detection with reapack-index
13+
-- - Split from original Quad Cortex MIDI Control script to create a more modular and maintainable codebase.
14+
-- - Added a setup wizard for first-time configuration.
15+
-- - Improved logging and error handling.
16+
-- - Automatically creates the MIDI output track if it doesn't exist.
17+
-- @provides
18+
-- [main] bertrandc_Quad Cortex MIDI control/bertrandc_Quad Cortex MIDI control (setup).lua
19+
-- [nomain] bertrandc_Quad Cortex MIDI control/lib.lua
520
-- @about
6-
-- # Quad Cortex MIDI Control
21+
-- # Quad Cortex MIDI control
722
-- Real-time MIDI control for Neural DSP Quad Cortex via Reaper Regions.
823
-- - Automates Presets using "#BankLetter" (e.g., #1A).
924
-- - Automates Scenes using "!Sx" (e.g., !S1 or !SA).
1025
-- - Auto-activates Gig View on Play and Tuner on Stop (both can be toggled).
26+
-- - **MIDI Clock Support**: Reaper will send tempo/clock to the Quad Cortex
27+
-- if "Send clock" is enabled for your MIDI output in Reaper's Preferences.
1128
--
1229
-- ## USAGE INSTRUCTIONS
1330
--
14-
-- 1. REAPER CONFIGURATION
15-
-- - Create a track (e.g., "MIDI QC CTRL").
16-
-- - Set Track Input to "MIDI > Virtual MIDI Keyboard > All Channels".
17-
-- - Add a "MIDI Hardware Output" on this track to your MIDI device (WIDI / Interface).
18-
-- - Record Arm and Input Monitoring must be ENABLED for the script to reach the QC.
31+
-- 1. AUTOMATIC INSTALLATION & SETUP
32+
-- - Run this script. If it's the first time, the SETUP WIZARD will open.
33+
-- - The script AUTOMATICALLY creates a dedicated MIDI track (Default: "Quad Cortex MIDI control").
34+
-- - This track is pre-configured: Armed, Monitoring ON, and Record DISABLED (Mode 2).
35+
-- - In the Wizard, select the correct MIDI Hardware Output ID for your QC.
1936
-- ---
20-
-- 2. SYNC YOUR PRESETS AND SCENES
21-
-- - Create Regions in Reaper named with the following tags:
22-
-- - PRESETS: Use "#BankLetter" format (e.g., "#1A", "#12C").
23-
-- - SCENES: Use "!Sx" where x is 1-8 or A-H (e.g., "!S1", "!SA").
24-
-- Note: Small "Scene" regions inside larger "Preset" regions are supported (Inheritance).
37+
-- 2. TEMPO & MIDI CLOCK SYNC
38+
-- - To sync Tempo (BPM) to your QC:
39+
-- - Preferences > MIDI Devices > Double-click Output > Check "Send clock to this device".
2540
-- ---
26-
-- 3. TOOLBAR INTEGRATION
27-
-- - Assign this script to a toolbar button. The button will light up when
28-
-- active. To stop, click again and select "Terminate Instance".
41+
-- 3. SYNC YOUR PRESETS AND SCENES
42+
-- - PRESETS: Use "#BankLetter" format (e.g., "#1A", "#12C").
43+
-- - SCENES: Use "!SA" to "!SH" (Matches QC Footswitches A to H).
2944
-- ---
30-
-- 4. ADDITIONAL FEATURES
31-
-- - PLAY: Automatically switches QC to "Gig View" and hides the Tuner.
32-
-- Set AUTO_GIGVIEW to false to disable this behavior.
33-
-- - STOP: Automatically activates the "Tuner" on the QC for silent breaks.
34-
-- Set AUTO_TUNER to false to disable this behavior.
35-
36-
-- ============================================================================
37-
-- CONFIGURATION & OPTIONS
38-
-- ============================================================================
39-
local MIDI_CHANNEL = 1
40-
local MIDI_OUTPUT_ID = 0
41-
42-
-- AUTOMATION OPTIONS
43-
local AUTO_GIGVIEW = true
44-
local AUTO_TUNER = true
45-
46-
-- LOG LEVEL (0: Silence, 1: Essential, 2: Full Debug)
47-
local DEBUG_LEVEL = 1
48-
49-
-- Initialize Toggle Button State
50-
local _, _, sectionID, cmdID = reaper.get_action_context()
51-
reaper.SetToggleCommandState(sectionID, cmdID, 1)
52-
reaper.RefreshToolbar2(sectionID, cmdID)
53-
54-
-- ============================================================================
55-
-- MIDI & LOGGING FUNCTIONS
56-
-- ============================================================================
57-
function Log(msg, level)
58-
if (level or 1) <= DEBUG_LEVEL then
59-
reaper.ShowConsoleMsg(tostring(msg) .. "\n")
45+
-- 4. LOG LEVELS
46+
-- - Log Level 1 [INFO]: Preset/Scene changes and Transport status.
47+
-- - Log Level 2 [DEBUG]: Full configuration details and file operations.
48+
49+
-- Main synchronization engine
50+
51+
local base_path = debug.getinfo(1).source:match("@?(.*[\\/])")
52+
local lib = dofile(base_path .. "bertrandc_Quad Cortex MIDI control/lib.lua")
53+
54+
-- --- INITIALIZATION ---
55+
reaper.ClearConsole()
56+
57+
if not lib.LoadSettings() then
58+
lib.Log("First run or missing config. Launching Setup Wizard...", 1)
59+
local setup_success = dofile(base_path .. "bertrandc_Quad Cortex MIDI control/bertrandc_Quad Cortex MIDI control (setup).lua")
60+
if not setup_success then
61+
lib.SetToolbarButtonState(0)
62+
return
6063
end
64+
lib.LoadSettings()
6165
end
6266

63-
function SendMIDI(status, data1, data2)
64-
local ch = MIDI_CHANNEL - 1
65-
reaper.StuffMIDIMessage(MIDI_OUTPUT_ID, status + ch, data1, data2)
67+
if not lib.EnsureControlTrack() then
68+
lib.SetToolbarButtonState(0)
69+
return
6670
end
6771

68-
function ConvertQCtoPC(bank, letter)
69-
local letters = {A=0, B=1, C=2, D=3, E=4, F=5, G=6, H=7}
70-
return ((tonumber(bank) - 1) * 8) + letters[letter:upper()]
71-
end
72+
lib.Log("Hardware Check: OK", 1)
7273

73-
-- ============================================================================
74-
-- REGION ANALYSIS
75-
-- ============================================================================
76-
function GetQCStateAtPos(pos)
77-
local num_markers = reaper.CountProjectMarkers(0)
78-
local state = { preset = nil, scene = nil, scene_dur = 9999999 }
79-
80-
for i = 0, num_markers - 1 do
81-
local _, isrgn, r_pos, r_end, r_name, _ = reaper.EnumProjectMarkers3(0, i)
82-
83-
if isrgn and pos >= r_pos and pos <= r_end then
84-
local match_found = false
85-
86-
local bank, letter = r_name:match("#(%d+)([A-Ha-h])")
87-
if bank and letter then
88-
state.preset = ConvertQCtoPC(bank, letter)
89-
state.preset_name = bank .. letter
90-
match_found = true
91-
end
92-
93-
local scene_map = {A=0, B=1, C=2, D=3, E=4, F=5, G=6, H=7}
94-
local s_match = r_name:match("!S([A-H1-8a-h])")
95-
96-
if s_match then
97-
local dur = r_end - r_pos
98-
if dur < state.scene_dur then
99-
state.scene_dur = dur
100-
if s_match:match("%d") then
101-
state.scene = tonumber(s_match) - 1
102-
else
103-
state.scene = scene_map[s_match:upper()]
104-
end
105-
state.scene_name = s_match:upper()
106-
match_found = true
107-
end
108-
end
109-
110-
if not match_found then
111-
Log(" [DEBUG] Region ignored: " .. r_name, 2)
112-
end
113-
end
114-
end
115-
return state
116-
end
74+
local lastPlayState, lastPc, lastCc = -1, -1, -1
11775

118-
-- ============================================================================
119-
-- MAIN LOOP
120-
-- ============================================================================
121-
local last_play_state = -1
122-
local last_sent_preset = -1
123-
local last_sent_scene = -1
124-
local last_debug_time = 0
125-
126-
function Main()
127-
local play_state = reaper.GetPlayState()
128-
local play_pos = (play_state == 0) and reaper.GetCursorPosition() or reaper.GetPlayPosition()
129-
130-
if play_state == 1 and (last_play_state == 0 or last_play_state == 2 or last_play_state == -1) then
131-
Log("► PLAYING: GigView ON / Tuner OFF", 1)
132-
if AUTO_TUNER then SendMIDI(0xB0, 45, 0) end
133-
if AUTO_GIGVIEW then SendMIDI(0xB0, 46, 127) end
134-
last_sent_preset, last_sent_scene = -1, -1
135-
elseif (play_state == 0 or play_state == 2) and last_play_state == 1 then
136-
Log("■ STOPPED: Tuner ON", 1)
137-
if AUTO_TUNER then SendMIDI(0xB0, 45, 127) end
138-
end
76+
function MainLoop()
77+
local playState = reaper.GetPlayState()
78+
local playPos = (playState == 0) and reaper.GetCursorPosition() or reaper.GetPlayPosition()
13979

140-
local current_time = reaper.time_precise()
141-
if DEBUG_LEVEL >= 2 and (current_time - last_debug_time > 1.0) then
142-
Log("[HEARTBEAT] Monitoring at " .. play_pos, 2)
143-
last_debug_time = current_time
80+
-- --- TRANSPORT HANDLING (Log Level 1) ---
81+
if playState == 1 and lastPlayState ~= 1 then
82+
local statusMsg = "Play"
83+
if lib.Config.AUTO_TUNER == "true" then
84+
lib.SendMidi(0xB0, 45, 0) -- Tuner OFF
85+
statusMsg = statusMsg .. " | Tuner: OFF"
86+
end
87+
if lib.Config.AUTO_GIGVIEW == "true" then
88+
lib.SendMidi(0xB0, 46, 127) -- GigView ON
89+
statusMsg = statusMsg .. " | GigView: ON"
90+
end
91+
lib.Log(statusMsg, 1)
92+
lastPc, lastCc = -1, -1
93+
94+
elseif (playState == 0 or playState == 2) and lastPlayState == 1 then
95+
local statusMsg = (playState == 0) and "Stop" or "Pause"
96+
if lib.Config.AUTO_TUNER == "true" then
97+
lib.SendMidi(0xB0, 45, 127) -- Tuner ON
98+
statusMsg = statusMsg .. " | Tuner: ON"
99+
end
100+
lib.Log(statusMsg, 1)
144101
end
145102

146-
local current = GetQCStateAtPos(play_pos)
103+
-- --- REGION PROCESSING ---
104+
local current = lib.GetProjectState(playPos)
147105

148-
if current.preset and current.preset ~= last_sent_preset then
149-
Log(" >>> Target Preset: #" .. current.preset_name, 1)
150-
SendMIDI(0xB0, 32, 1)
151-
SendMIDI(0xC0, current.preset, 0)
152-
last_sent_preset = current.preset
153-
last_sent_scene = -1
106+
if current.pc and current.pc ~= lastPc then
107+
lib.SendMidi(0xB0, 32, 1)
108+
lib.SendMidi(0xC0, current.pc, 0)
109+
lastPc, lastCc = current.pc, -1
110+
lib.Log("Preset Change -> " .. current.pc_name, 1)
154111
end
155112

156-
if current.scene and current.scene ~= last_sent_scene then
157-
Log(" >>> Target Scene: !S" .. current.scene_name, 1)
158-
SendMIDI(0xB0, 43, current.scene)
159-
last_sent_scene = current.scene
113+
if current.cc and current.cc ~= lastCc then
114+
lib.SendMidi(0xB0, 43, current.cc)
115+
lastCc = current.cc
116+
lib.Log("Scene Change -> Scene " .. current.cc_name, 1)
160117
end
161118

162-
last_play_state = play_state
163-
reaper.defer(Main)
119+
lastPlayState = playState
120+
reaper.defer(MainLoop)
164121
end
165122

166-
function OnExit()
167-
reaper.SetToggleCommandState(sectionID, cmdID, 0)
168-
reaper.RefreshToolbar2(sectionID, cmdID)
169-
Log("QC MIDI Control: DEACTIVATED", 1)
170-
end
123+
-- --- EXECUTION ---
124+
lib.SetToolbarButtonState(1)
125+
reaper.atexit(lib.HandleExit)
171126

172-
reaper.atexit(OnExit)
173-
reaper.ClearConsole()
174-
Log("QC MIDI Control 1.0 - Ready", 1)
175-
Main()
127+
lib.Log("Engine Active (MIDI Track: " .. lib.Config.TRACK_NAME .. ")", 1)
128+
lib.Log("Note: Enable 'Send clock' in MIDI Prefs for Tempo Sync.", 1)
129+
130+
MainLoop()
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
-- @noindex
2+
3+
-- @description Setup Wizard for Quad Cortex MIDI control
4+
-- @author Bertrand C
5+
6+
local base_path = debug.getinfo(1).source:match("@?(.*[\\/])")
7+
local lib = dofile(base_path .. "lib.lua")
8+
9+
function RunSetupWizard()
10+
lib.LoadSettings()
11+
local s = lib.Config
12+
13+
local midi_list = lib.GetMidiOutputsList()
14+
reaper.ClearConsole()
15+
reaper.ShowConsoleMsg(midi_list)
16+
reaper.ShowConsoleMsg("\nLook at the list above to find your MIDI Hardware ID.\n")
17+
18+
-- extrawidth=200 for better readability the dedicated track name that can be quite long
19+
local captions = "extrawidth=200,MIDI Channel (1-16),Hardware MIDI Out ID (see Console),Dedicated Track Name,Preset Prefix,Scene Prefix,Tuner on Stop? (y/n),GigView on Play? (y/n),Log Level (0-2)"
20+
21+
local csv = string.format("%s,%s,%s,%s,%s,%s,%s,%s",
22+
s.MIDI_CHANNEL, s.MIDI_OUTPUT_ID, s.TRACK_NAME, s.PRESET_PREFIX, s.SCENE_PREFIX,
23+
(s.AUTO_TUNER == "true" and "y" or "n"), (s.AUTO_GIGVIEW == "true" and "y" or "n"), s.LOG_LEVEL)
24+
25+
local retval, user_input = reaper.GetUserInputs("Quad Cortex Setup", 8, captions, csv)
26+
27+
if retval then
28+
local ch, id, name, p_pre, p_sce, tuner, gig, log = user_input:match("([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*)")
29+
30+
local updated = {
31+
MIDI_CHANNEL = ch,
32+
MIDI_OUTPUT_ID = id,
33+
TRACK_NAME = name:match("^%s*(.-)%s*$"),
34+
PRESET_PREFIX = p_pre,
35+
SCENE_PREFIX = p_sce,
36+
AUTO_TUNER = (tuner:lower():find("y") or tuner:lower() == "true") and "true" or "false",
37+
AUTO_GIGVIEW = (gig:lower():find("y") or gig:lower() == "true") and "true" or "false",
38+
LOG_LEVEL = log
39+
}
40+
41+
lib.SaveSettings(updated)
42+
43+
local helpMsg = "Configuration saved successfully!\n\n" ..
44+
"--- QUICK START ---\n" ..
45+
"1. Right-click on any Toolbar > Customize toolbar...\n" ..
46+
"2. Click 'Add...' and search for: Quad_Cortex_MIDI_control\n" ..
47+
"3. Select it and click 'Select/Close'.\n\n" ..
48+
"--- CHANGING SETTINGS ---\n" ..
49+
"To modify your configuration later, simply run the setup action from the Action List:\n" ..
50+
"Action: Quad_Cortex_MIDI_control_setup\n\n" ..
51+
"The main button will light up when the synchronization is active."
52+
53+
reaper.ShowMessageBox(helpMsg, "QC MIDI Control - Setup Complete", 0)
54+
55+
lib.EnsureControlTrack()
56+
57+
return true
58+
end
59+
return false
60+
end
61+
62+
return RunSetupWizard()

0 commit comments

Comments
 (0)