From c6f5a26d9dd9e96710aebe3f1d564fa60b559c1f Mon Sep 17 00:00:00 2001 From: Flynn <46948713+rexendevar@users.noreply.github.com> Date: Tue, 21 Apr 2026 15:05:11 -0700 Subject: [PATCH 1/4] Release Gamepad Controller for Reaper v1.0 Initial upload --- .../rxfx_Hotpluggable Gamepad Controller.eel | 412 ++++++++++++++++++ 1 file changed, 412 insertions(+) create mode 100644 Various/rxfx_Hotpluggable Gamepad Controller.eel diff --git a/Various/rxfx_Hotpluggable Gamepad Controller.eel b/Various/rxfx_Hotpluggable Gamepad Controller.eel new file mode 100644 index 000000000..7c0778aaa --- /dev/null +++ b/Various/rxfx_Hotpluggable Gamepad Controller.eel @@ -0,0 +1,412 @@ +// @description Gamepad Controller for Reaper +// @author Rek's Effeks +// @version 1.0 +// @changelog Initial upload +// @about +// Hotplug-enabled gamepad controller script for Reaper. Requires SWS extension. Simply run the script, plug in your controller and let it do its thing. DISCLAIMER: developed on Linux, so all the button codes and stuff MIGHT be different. +// +// Controls: +// +// - A - redo +// - B - undo +// - X - record +// - Y - play/stop +// - Bumpers - skip the cursor between markers +// - Main/guide button - all notes off + reset midi controllers +// - D-pad - select track (left-right moves by 1, up-down skips to top or bottom of the tracklist) +// +// SELECT button plus: +// - A - new track +// - B - delete selected track +// - X - record-arm mode +// - Y - nothing +// - Right bumper - tap tempo +// +// When in record-arm mode, press X or Y to arm input 1 or 2; B to disarm the selected track; the main/guide button to disarm all tracks; or A to cancel. +// +// START button plus: +// - A - new marker at playhead (snapped to grid) +// - B - delete closest marker +// - X - time-select mode +// - Y - toggle repeat +// - Bumpers - skip to start or end of project +// - D-pad horizontal - enable/disable metronome (right means on) +// - D-pad vertical - enable/disable pre-roll before record (up means on) +// +// When entering time-select mode, you're moved to the start of the project. From there you can marker-skip to one of your loop points using the bumpers, then press X to confirm it. Use the same controls to select and confirm your second point (it won't send you to the start of the project the second time), or press A to cancel anytime. Your previous edit position will restore. +// +// START AND SELECT plus: +// - Right bumper - graft in selection time (extend project) +// - Left bumper - chop out selection time (shorten project) +// +// Adapted from Ole Kirkeby's Xbox One joystick script from January 2019. + + +// Button codes, constants given by the system. Do not change +BUTTON_A = 1; +BUTTON_B = 2; +BUTTON_X = 4; +BUTTON_Y = 8; +BUTTON_LB = 16; +BUTTON_RB = 32; +BUTTON_SELECT = 64; +BUTTON_START = 128; +BUTTON_MAIN = 256; +BUTTON_JOY_L = 512; +BUTTON_JOY_R = 1024; + +// Axis bindings. Given by the system +// Joystick L x = axis(0), -1 <= out <= 1 +// Joystick L y = axis(1), -1 <= out <= 1 positive downwards +// Joystick R x = axis(3), -1 <= out <= 1 +// Joystick R y = axis(4), -1 <= out <= 1 positive downwards +// Trigger L = axis(2) positive, 0 <= out <= 1 +// Trigger R = axis(2) negative, -1 <= out <= 0 + +// Control change messages. Change as desired +// CC 20-31 are unallocated +TRIGGER_CC = 20; + + +guid = 1; +oldaxis = 100; // strings/array addresses + + + +function axis(ax) +( + 0[ax]; +); + +function str(n) +( + x = #; + sprintf(x,"%f", n); + x; +); + +function r(command) +( + Main_OnCommand(command, 0); +); + +function n(command) +( + Main_OnCommand( NamedCommandLookup(command), 0); +); + +function setup() +( + // Joystick setup + joystick_enum(guid, 0); + joystick_index = 0; + + // Personal setup preference + match("*0000*", guid) ? ( + joystick_enum(guid, 1); + joystick_index = 1; + ); + + (strlen(guid)==0) ? ( + defer("setup()"); + ):( + xbox = joystick_create(guid); + joystick_getinfo(xbox, numaxis); + i = 0; + loop(numaxis, + axis(i) = joystick_getaxis(xbox, i); + oldaxis[i] = axis(i); + i += 1; + ); + oldbuttons = joystick_getbuttonmask(xbox); + + defer("main_loop()"); + ); +); + +function main_loop() +( + // really weird method to check if controller still plugged in + // far as i know this is The Only way to do so + i=0; + loop(5, + numbr = #numbr; + joystick_enum(#numbr, i); + i == joystick_index ? ( + strlen(#numbr) > 0 ? ( + working = 1; + ):( + working = 0; + ); + ); + i += 1; + ); + + // if it's still plugged in + working ? ( + // refresh info + joystick_update(xbox); + buttons = joystick_getbuttonmask(xbox); + i = 0; + loop(numaxis, + 0[i] = joystick_getaxis(xbox, i); + i+=1; + ); + + // deal with buttons + oldbuttons != buttons ? ( + + // a + // redo, new track, create marker + (buttons & BUTTON_A) != (oldbuttons & BUTTON_A) ? ( + (buttons & BUTTON_A) ? ( // only run when pressed not released + (buttons & BUTTON_SELECT) ? ( + r(40702); // new track at end of tcp + ):( + (buttons & BUTTON_START) ? ( + Undo_BeginBlock(); + r(40157); // marker @ playhead + n("_BR_CLOSEST_PROJ_MARKER_PLAY_SNAP"); // snap it in place + r(40898); // renumber markers etc + Undo_EndBlock("Make new snapped marker", 0); + ):( + RecCycleActive ? ( + RecCycleActive = 0; // cancel + ):( + MarkerSelActive ? ( + n("_BR_RESTORE_CURSOR_POS_SLOT_2"); + MarkerSelActive = 0; // cancel + ):( + r(40030); // redo + ); + ); + ); + ); + ); + ); + + // b + // undo, delete track, delete marker + (buttons & BUTTON_B) != (oldbuttons & BUTTON_B) ? ( + (buttons & BUTTON_B) ? ( + (buttons & BUTTON_SELECT) ? ( + r(40005); // delete track + ):( + (buttons & BUTTON_START) ? ( + Undo_BeginBlock(); + n("_BR_SAVE_CURSOR_POS_SLOT_1"); // save edit cursor pos + r(40434); // move edit cursor 2 playhead + r(40613); // dlete marker near cursor + n("_BR_RESTORE_CURSOR_POS_SLOT_1"); + r(40898); // renumber markers etc + Undo_EndBlock("Delete closest marker", 0); + ):( + RecCycleActive ? ( + tr = GetSelectedTrack(0,0); + SetMediaTrackInfo_Value(tr, "I_RECARM", 0); // set selected track to disarmed + RecCycleActive = 0; + ):( + r(40029); // undo + ); + ); + ); + ); + ); + + // x + // record, start record cycle, set loop edit + (buttons & BUTTON_X) != (oldbuttons & BUTTON_X) ? ( + (buttons & BUTTON_X) ? ( + (buttons & BUTTON_SELECT) ? ( + RecCycleActive = 1; + ):( + (buttons & BUTTON_START) ? ( + MarkerSelActive = 1; + FirstTime = 0; + SecondTime = 0; + MSelStage = 0; + n("_BR_SAVE_CURSOR_POS_SLOT_2"); + r(40042); // go to project start + ):( + RecCycleActive ? ( + tr = GetSelectedTrack(0,0); + SetMediaTrackInfo_Value(tr, "I_RECINPUT", 0); // set selected track to record arm channel 1 + SetMediaTrackInfo_Value(tr, "I_RECARM", 1); + RecCycleActive = 0; + ):( + MarkerSelActive ? ( // if currently in marker select mode, + MSelStage == 0 ? ( + FirstTime = GetCursorPosition(); // lock in the first position (edit cursor) + MSelStage = 1; // and move to stage 2 + ):( + SecondTime = GetCursorPosition(); + GetSet_LoopTimeRange(1, 0, FirstTime, SecondTime, 1); // set time selection and return to normal + n("_BR_RESTORE_CURSOR_POS_SLOT_2"); + MarkerSelActive = 0; + ); + ):( + r(1013); // record + ); + ); + ); + ); + ); + ); + + // y button + // play/stop, nothing, toggle repeat + (buttons & BUTTON_Y) != (oldbuttons & BUTTON_Y) ? ( + (buttons & BUTTON_Y) ? ( + (buttons & BUTTON_SELECT) ? ( + //buttonCC(BUTTON_Y, CC_Y1); + 0; // nothing yet + ):( + (buttons & BUTTON_START) ? ( + r(1068); // toggle repeat + ):( + RecCycleActive ? ( + tr = GetSelectedTrack(0,0); + SetMediaTrackInfo_Value(tr, "I_RECINPUT", 1); // set selected track to record arm channel 2 + SetMediaTrackInfo_Value(tr, "I_RECARM", 1); + RecCycleActive = 0; + ):( + r(40044); // play/stop + ); + ); + ); + ); + ); + + // bumpers - skip markers, tap tempo, go to start/end, insert/remove selection time (start + select) + (buttons & BUTTON_LB) != (oldbuttons & BUTTON_LB) ? ( + (buttons & BUTTON_LB) ? ( + (buttons & BUTTON_START) ? ( + (buttons & BUTTON_SELECT) ? ( + r(40201); // chop out selection + ):( + r(40042); // go to start + ); + ):( + r(40172); // prev marker or start + ); + ); + ); + (buttons & BUTTON_RB) != (oldbuttons & BUTTON_RB) ? ( + (buttons & BUTTON_RB) ? ( + (buttons & BUTTON_START) ? ( + (buttons & BUTTON_SELECT) ? ( + r(40200); // graft in selection + ):( + r(40043); // go to end (start only) + ); + ):( + (buttons & BUTTON_SELECT) ? ( + r(1134); // tap tempo (select only) + ):( + r(40173); // next marker or end (no modifier) + ); + ); + ); + ); + + // main button - reset controllers, all notes off, disarm all tracks + (buttons & BUTTON_MAIN) != (oldbuttons & BUTTON_MAIN) ? ( + (buttons & BUTTON_MAIN) ? ( + RecCycleActive ? ( + r(40491); // disarm all tracks + RecCycleActive = 0; + ):( + r(40345); // all notes off to everything + r(41175); // reset all midi devices + ); + ); + ); + ); + + // dpad - various movement controls + oldaxis[6] != axis(6) ? ( // horizontal + value = 127 * axis(6); + value < 0 ? ( + (buttons & BUTTON_START) ? ( + r(41746); // disable metronome + ):( + r(40286); // go to prev track + ); + ); + value > 0 ? ( + (buttons & BUTTON_START) ? ( + r(41745); // enable metronome + ):( + r(40285); // go to next track + ); + ); + ); + + oldaxis[7] != axis(7) ? ( // vertical + value = 127 * axis(7); + value > 0 ? ( + (buttons & BUTTON_START) ? ( + GetToggleCommandState(41819) ? ( + r(41819); // disable pre-roll if it's enabled + ); + ):( + MarkerSelActive ? ( + r(40043); // go to project end + ):( + r(40296); // select last track + n("_XENAKIOS_SELLASTOFSELTRAX"); + ); + ); + ); + value < 0 ? ( + (buttons & BUTTON_START) ? ( + !GetToggleCommandState(41819) ? ( + r(41819); // enable pre-roll if it's disabled + ); + ):( + MarkerSelActive ? ( + r(40042); // go to project start + ):( + r(40296); // select first track + n("_XENAKIOS_SELFIRSTOFSELTRAX"); + ); + ); + ); + ); + // deprecated midi stuff + /*oldaxis[0] != axis(0) ? ( + value = (axis(0) + 1) * 8192; + value < 0 ? value = 0 : value > 16383 ? value = 16383; + //event(MIDI_CONTROL_CHANGE * 16 + CHAN, CC_JOY_L_WE, (value / 128) | 0); // MSB + //event(MIDI_CONTROL_CHANGE * 16 + CHAN, CC_JOY_L_WE + 32, value & 127); // LSB + ); + + oldaxis[1] != axis(1) ? ( + value = (axis(1) + 1) * 8192; + value < 0 ? value = 0 : value > 16383 ? value = 16383; + //event(MIDI_CONTROL_CHANGE * 16 + CHAN, CC_JOY_L_NS, (value / 128) | 0); // MSB + //event(MIDI_CONTROL_CHANGE * 16 + CHAN, CC_JOY_L_NS + 32, value & 127); // LSB + ); + + (oldaxis[2] != axis(2)) || (oldaxis[5] != axis(5)) ? ( + value = 64 * (axis(5)-axis(2)) + 64; + //event(MIDI_CONTROL_CHANGE * 16 + CHAN, TRIGGER_CC, value); + );*/ + + + // log the frame & rerun + oldbuttons = buttons; + i=0; + loop(numaxis, + oldaxis[i] = axis(i); + i+=1; + ); + defer("main_loop()"); + ):( + // if controller no longer in guid list + joystick_destroy(xbox); + defer("setup()"); + ); +); + +setup(); From 1989b336b6463767b91e368f2a51c3b6d5c0e8e2 Mon Sep 17 00:00:00 2001 From: Flynn <46948713+rexendevar@users.noreply.github.com> Date: Tue, 21 Apr 2026 17:57:06 -0700 Subject: [PATCH 2/4] Rename rxfx_Hotpluggable Gamepad Controller.eel to rxfx_Gamepad controller for Reaper.eel --- ...epad Controller.eel => rxfx_Gamepad controller for Reaper.eel} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Various/{rxfx_Hotpluggable Gamepad Controller.eel => rxfx_Gamepad controller for Reaper.eel} (100%) diff --git a/Various/rxfx_Hotpluggable Gamepad Controller.eel b/Various/rxfx_Gamepad controller for Reaper.eel similarity index 100% rename from Various/rxfx_Hotpluggable Gamepad Controller.eel rename to Various/rxfx_Gamepad controller for Reaper.eel From 75637227f3b833c2e9ce399217726db82c9c5a0f Mon Sep 17 00:00:00 2001 From: rexendevar Date: Fri, 24 Apr 2026 17:53:38 -0700 Subject: [PATCH 3/4] Windows support & trigger scrubbing --- .../rxfx_Gamepad controller for Reaper.eel | 192 ++++++++++++------ 1 file changed, 131 insertions(+), 61 deletions(-) diff --git a/Various/rxfx_Gamepad controller for Reaper.eel b/Various/rxfx_Gamepad controller for Reaper.eel index 7c0778aaa..eb98dc61e 100644 --- a/Various/rxfx_Gamepad controller for Reaper.eel +++ b/Various/rxfx_Gamepad controller for Reaper.eel @@ -1,7 +1,7 @@ // @description Gamepad Controller for Reaper // @author Rek's Effeks -// @version 1.0 -// @changelog Initial upload +// @version 1.1 +// @changelog Added Windows support and trigger scrubbing // @about // Hotplug-enabled gamepad controller script for Reaper. Requires SWS extension. Simply run the script, plug in your controller and let it do its thing. DISCLAIMER: developed on Linux, so all the button codes and stuff MIGHT be different. // @@ -12,6 +12,7 @@ // - X - record // - Y - play/stop // - Bumpers - skip the cursor between markers +// - Triggers - scrub the playhead // - Main/guide button - all notes off + reset midi controllers // - D-pad - select track (left-right moves by 1, up-down skips to top or bottom of the tracklist) // @@ -42,40 +43,119 @@ // Adapted from Ole Kirkeby's Xbox One joystick script from January 2019. -// Button codes, constants given by the system. Do not change -BUTTON_A = 1; -BUTTON_B = 2; -BUTTON_X = 4; -BUTTON_Y = 8; -BUTTON_LB = 16; -BUTTON_RB = 32; -BUTTON_SELECT = 64; -BUTTON_START = 128; -BUTTON_MAIN = 256; -BUTTON_JOY_L = 512; -BUTTON_JOY_R = 1024; -// Axis bindings. Given by the system -// Joystick L x = axis(0), -1 <= out <= 1 -// Joystick L y = axis(1), -1 <= out <= 1 positive downwards -// Joystick R x = axis(3), -1 <= out <= 1 -// Joystick R y = axis(4), -1 <= out <= 1 positive downwards -// Trigger L = axis(2) positive, 0 <= out <= 1 -// Trigger R = axis(2) negative, -1 <= out <= 0 +guid = #guid; // strings/array addresses +os = #os; +newaxis = 0; +oldaxis = 50; +newpov = 100; +oldpov = 150; +oldfunc = 200; // last frame's triggers & dpads +GetOS(#os); -// Control change messages. Change as desired -// CC 20-31 are unallocated -TRIGGER_CC = 20; +function axis(ax) +( + newaxis[ax]; +); -guid = 1; -oldaxis = 100; // strings/array addresses +function TRIGGERS() // triggers & d-pad behave differently on win vs linux +( + match("Win*",#os) ? ( + newaxis[2]; // they're one axis on Win + ):( + ( newaxis[2]-newaxis[5] ) /2; // two separate axes on Linux + ); +); +function DPAD_H() +( + match("Win*",#os) ? ( + newpov[0] == 655.35 ? 0 : sin( newpov[0] * $pi/180 ); + ):( + newaxis[6]; + ); +); +function DPAD_V() +( + match("Win*",#os) ? ( + newpov[0] == 655.35 ? 0 : -cos( newpov[0] * $pi/180 ); + ):( + newaxis[7]; + ); +); -function axis(ax) +function refresh_funcs() +( + oldfunc[0] = TRIGGERS(); + oldfunc[1] = DPAD_H(); + oldfunc[2] = DPAD_V(); +); + +function reload(xbox, fromscratch) ( - 0[ax]; + joystick_update(xbox); + i = 0; + loop(numaxis, + newaxis[i] = joystick_getaxis(xbox, i); + fromscratch ? oldaxis[i] = newaxis[i]; + i += 1; + ); + i = 0; + loop(numpov, + newpov[i] = joystick_getpov(xbox, i); + fromscratch ? oldpov[i] = newpov[i]; + i += 1; + ); + buttons = joystick_getbuttonmask(xbox); + fromscratch ? oldbuttons = buttons; + fromscratch ? refresh_funcs(); +); + +match("Win*",#os) ? ( + // Windows bindings + BUTTON_A = 1; + BUTTON_B = 2; + BUTTON_X = 4; + BUTTON_Y = 8; + BUTTON_LB = 16; + BUTTON_RB = 32; + BUTTON_SELECT = 64; + BUTTON_START = 128; + BUTTON_JOY_L = 256; + BUTTON_JOY_R = 512; + BUTTON_MAIN = 1024; + + JOY_L_H = 0; + JOY_L_V = 1; + // triggers; + JOY_R_H = 3; + JOY_R_V = 4; + + // dpad is on a POV for windows +):( + // Linux bindings + BUTTON_A = 1; + BUTTON_B = 2; + BUTTON_X = 4; + BUTTON_Y = 8; + BUTTON_LB = 16; + BUTTON_RB = 32; + BUTTON_SELECT = 64; + BUTTON_START = 128; + BUTTON_MAIN = 256; // last 3 are different + BUTTON_JOY_L = 512; + BUTTON_JOY_R = 1024; + + JOY_L_H = 0; + JOY_L_V = 1; + // left trigger; + JOY_R_H = 3; + JOY_R_V = 4; + // right trigger; + // dpad horiz; + // dpad vert; ); function str(n) @@ -98,28 +178,21 @@ function n(command) function setup() ( // Joystick setup - joystick_enum(guid, 0); + joystick_enum(#guid, 0); joystick_index = 0; // Personal setup preference - match("*0000*", guid) ? ( - joystick_enum(guid, 1); + /*match("*0000*", #guid) ? ( + joystick_enum(#guid, 1); joystick_index = 1; - ); + );*/ - (strlen(guid)==0) ? ( + (strlen(#guid)==0) ? ( defer("setup()"); ):( - xbox = joystick_create(guid); - joystick_getinfo(xbox, numaxis); - i = 0; - loop(numaxis, - axis(i) = joystick_getaxis(xbox, i); - oldaxis[i] = axis(i); - i += 1; - ); - oldbuttons = joystick_getbuttonmask(xbox); - + xbox = joystick_create(#guid); + joystick_getinfo(xbox, numaxis, numpov); + reload(xbox, 1); defer("main_loop()"); ); ); @@ -144,14 +217,7 @@ function main_loop() // if it's still plugged in working ? ( - // refresh info - joystick_update(xbox); - buttons = joystick_getbuttonmask(xbox); - i = 0; - loop(numaxis, - 0[i] = joystick_getaxis(xbox, i); - i+=1; - ); + reload(xbox, 0); // deal with buttons oldbuttons != buttons ? ( @@ -324,8 +390,8 @@ function main_loop() ); // dpad - various movement controls - oldaxis[6] != axis(6) ? ( // horizontal - value = 127 * axis(6); + oldfunc[1] != DPAD_H() ? ( // horizontal + value = 127 * DPAD_H(); value < 0 ? ( (buttons & BUTTON_START) ? ( r(41746); // disable metronome @@ -342,8 +408,8 @@ function main_loop() ); ); - oldaxis[7] != axis(7) ? ( // vertical - value = 127 * axis(7); + oldfunc[2] != DPAD_V() ? ( // vertical + value = 127 * DPAD_V(); value > 0 ? ( (buttons & BUTTON_START) ? ( GetToggleCommandState(41819) ? ( @@ -386,25 +452,29 @@ function main_loop() value < 0 ? value = 0 : value > 16383 ? value = 16383; //event(MIDI_CONTROL_CHANGE * 16 + CHAN, CC_JOY_L_NS, (value / 128) | 0); // MSB //event(MIDI_CONTROL_CHANGE * 16 + CHAN, CC_JOY_L_NS + 32, value & 127); // LSB - ); - - (oldaxis[2] != axis(2)) || (oldaxis[5] != axis(5)) ? ( - value = 64 * (axis(5)-axis(2)) + 64; - //event(MIDI_CONTROL_CHANGE * 16 + CHAN, TRIGGER_CC, value); );*/ + CSurf_ScrubAmt(-4*TRIGGERS()); + // log the frame & rerun oldbuttons = buttons; i=0; loop(numaxis, - oldaxis[i] = axis(i); + oldaxis[i] = newaxis[i]; + i+=1; + ); + i=0; + loop(numpov, + oldpov[i] = newpov[i]; i+=1; ); + refresh_funcs(); defer("main_loop()"); ):( // if controller no longer in guid list joystick_destroy(xbox); + xbox = 0; defer("setup()"); ); ); From a9c01059217402b4f19ce44517bee3b5495650bb Mon Sep 17 00:00:00 2001 From: Christian Fillion Date: Fri, 24 Apr 2026 21:12:40 -0400 Subject: [PATCH 4/4] sentence case + s/Reaper/REAPER/ --- ...er for Reaper.eel => rxfx_Gamepad controller for REAPER.eel} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename Various/{rxfx_Gamepad controller for Reaper.eel => rxfx_Gamepad controller for REAPER.eel} (99%) diff --git a/Various/rxfx_Gamepad controller for Reaper.eel b/Various/rxfx_Gamepad controller for REAPER.eel similarity index 99% rename from Various/rxfx_Gamepad controller for Reaper.eel rename to Various/rxfx_Gamepad controller for REAPER.eel index eb98dc61e..741509a36 100644 --- a/Various/rxfx_Gamepad controller for Reaper.eel +++ b/Various/rxfx_Gamepad controller for REAPER.eel @@ -1,4 +1,4 @@ -// @description Gamepad Controller for Reaper +// @description Gamepad controller for REAPER // @author Rek's Effeks // @version 1.1 // @changelog Added Windows support and trigger scrubbing