No internet connection
  1. Home
  2. How to

Clip EQ with Knobs (Eucon or Midi or Streamdeck +)

By Owen Granich-Young @Owen_Granich_Young
    2023-09-16 18:09:04.979Z

    My kingdom for Clip EQ on Knobs of some sort.... Anybody have even a really Janky version of this?

    • 44 replies

    There are 44 replies. Estimated reading time: 49 minutes

    1. O

      Hell I'll just take HP and LP frequency...

      1. Chad Wahlbrink @Chad2023-09-18 22:15:42.290Z

        Hey @Owen_Granich_Young!

        Here's an idea to possibly run with. I was able to create a pretty "simple" filter control using Generic Knob Commands and the Stream Deck Plus.


        The Scripts are as follows:

        Decrement

        let clipEffectsPanel = sf.ui.proTools.windows.whoseTitle.startsWith('Edit: ').first.children.whoseTitle.is('Clip Effects');
        clipEffectsPanel.first.children.whoseTitle.is('Filter 1 Frequency').first.elementClick({actionName:"AXDecrement"});
        

        Increment
        let clipEffectsPanel = sf.ui.proTools.windows.whoseTitle.startsWith('Edit: ').first.children.whoseTitle.is('Clip Effects');
        clipEffectsPanel.first.children.whoseTitle.is('Filter 1 Frequency').first.elementClick({actionName:"AXIncrement"});
        

        Toggle In/Out
        let clipEffectsPanel = sf.ui.proTools.windows.whoseTitle.startsWith('Edit: ').first.children.whoseTitle.is('Clip Effects');
        
        if(clipEffectsPanel.first.children.whoseTitle.is('Filter 1 In/Out').first.value.intValue == 0){
            clipEffectsPanel.first.children.whoseTitle.is('Filter 1 In/Out').first.elementClick({actionName:"AXIncrement"});
        } else{
            clipEffectsPanel.first.children.whoseTitle.is('Filter 1 In/Out').first.elementClick({actionName:"AXDecrement"});
        }
        

        If you make 3 separate scripts, map them to a generic knob command, you will be off to the races.

        1. Sweet bud I'll take a look at your build! I bet it's better than mine is working. I've been building with relative ui click and key press down and up

          1. In reply toChad:

            Hmm is there a way to multiply the speed? so like AXIncrement*2? Here's a quick video of yours on HP and mine on Low Pass Wondering if I can use the more robust one that is yours but with the speed of my down arrow relative click?

            1. Reading around the Forum seems the only solution is to just trigger it twice... still feels a little clunkier then the down arrow simulation but I think I'll live with it since it's a more stable option. If there's a 'faster' way to repeat AXincrement (like fast repetitions for key presses) I'd love to know that tech.

              1. Chad Wahlbrink @Chad2023-09-19 00:21:16.539Z

                @Owen_Granich_Young, the "AXIncrement" method is a bit limiting overall, but it is addressable UI, at least!

                sf.ui.proTools.mainWindow.childrenByRole('AXGroup').whoseTitle.is('Clip Effects').first.elementDebug();
                

                ↑ If you use elementDebug() you can get a full list of the UI elements I'm addressing.

                Then open the log file and you'll have the list there!


                This will give you this list: https://www.dropbox.com/scl/fi/spcf20affdz24r03yx5yn/Clip-Effects-deBug.rtf?rlkey=0eonsxo9fv0t4mr5konhm4wpl&dl=0

                1. Aha, elementDebug() very very cool, thank you!

                  1. Chad Wahlbrink @Chad2023-09-19 01:31:43.146Z

                    No problem! It's a fun method. After doing more digging, it seems that these sliders are not "settable." - you can find what values are settable by using the attributes argument in elementDebug(). This basically just means, you are limited to either using "AXIncrement" and "AXDecrement" or you can utilize relative mouse simulation like you were exploring to either use "up" and "down" arrow keys or use a scroll action simulation.

                    let clipEffectsPanel = sf.ui.proTools.mainWindow.children.whoseTitle.is('Clip Effects');
                    clipEffectsPanel.first.children.whoseTitle.is('Filter 1 Frequency').first.elementDebug({attributes:true});
                    

                    ↑ This showed me the following output:

                    "AXValue": {
                        "class": "__NSCFNumber",
                        "isSettable": false
                      },
                      "AXValueDescription": {
                        "class": "NSTaggedPointerString",
                        "isSettable": false,
                        "value": "20.6 Hz"
                      },
                    

                    Thus not settable.

                    1. Hey @Chad it's me again!

                      So is the above the reason :

                      let clipEffectsPanel = sf.ui.proTools.windows.whoseTitle.startsWith('Edit: ').first.children.whoseTitle.is('Clip Effects');
                      
                      clipEffectsPanel.first.children.whoseTitle.is('Filter 1 Frequency').first.mouseClickElement();
                      

                      Won't work? I was hoping to dodge Click Relative to UI element but use the mouseClickElement(); to click into the text box...

                      1. Chad Wahlbrink @Chad2023-09-19 17:40:41.421Z

                        It may be! @Kitch has more experience here. So I'm curious if he can chime in and confirm that an element that is "not settable" is also "not-clickable" by mouse simulation.

                        But yes, I ran into that same issue... I haven't found a workaround yet unfortunately.

                        1. OOwen Granich-Young @Owen_Granich_Young
                            2023-09-19 17:42:52.601Z2023-09-19 18:02:08.874Z

                            Yeah the workaround seems to be Click Relative UI

                            const {parameter, boxValue} = event.props
                            
                            const positionLookup = {
                                "High-Mid Frequency" : {"x":505,"y":75},
                                "High-Mid Gain" : {"x":555,"y":75},
                                "High-Mid Q" : {"x":605,"y":75}
                            }
                            
                            const selectedPosition = positionLookup[parameter];
                            
                            sf.ui.proTools.mainWindow.children.whoseRole.is("AXStaticText").whoseValue.is('Clip:').first.mouseClickElement({
                                relativePosition: selectedPosition,
                            });
                            
                                sf.keyboard.type({ text: boxValue });
                            
                                sf.keyboard.press({ keys: "numpad enter", });
                            

                            A lot of brute force to be programmed to affect every parameter... snappy though

                            This is gonna take a while
              2. In reply toChad:

                Ps how do I get the info on the other children.whoseTitle.is ? such as these elements

                1. In reply toChad:

                  @Chad Ok, so going back to the original idea... I think that scroll wheel approach would be the snappiest solution. I've built the lookup table of XY relative to UI. My question to you is, what's the 'most stable' way (yes I know it's not stable) to 'float mouse over postion' 'Simulate Scroll Wheel'

                  Because watch this action... this is how I'd love the knobs to perform.
                  https://www.dropbox.com/scl/fi/men5pdtvx3fywr34ohxln/Float-and-Scroll-Wheel-Up-Down.mov?rlkey=x4rr6n8scu4h0e4tmrevgvou4&dl=0

                  XY table

                  const {parameter, boxValue} = event.props
                  
                  const positionLookup = {
                      "High Frequency" : {"x":505,"y":75},
                      "High Gain" : {"x":555,"y":75},
                      "High Q" :  {"x":605,"y":75},
                      "High-Mid Frequency" : {"x":505,"y":75},
                      "High-Mid Gain" : {"x":555,"y":75},
                      "High-Mid Q" : {"x":605,"y":75},
                      "Low-Mid Frequency" : {"x":505,"y":145},
                      "Low-Mid Gain" : {"x":555,"y":145},
                      "Low-Mid Q" : {"x":605,"y":145},
                      "Low Frequency" :  {"x":505,"y":175},
                      "Low Gain" : {"x":555,"y":175},
                      "Low Q" : {"x":605,"y":175},
                      "Filter 1 Freq" : {"x":740,"y":90},
                      "Filter 2 Freq" : {"x":737,"y":154},
                  }
                  
                  const selectedPosition = positionLookup[parameter];
                  
                  ////Help code here to float over above XY and I guess seprate script for up down scroll wheel? Not sure best approach
                  
                  1. Chad Wahlbrink @Chad2023-09-19 20:24:11.856Z

                    Hey @Owen_Granich_Young

                    I'm using some code found here:
                    How move the Mouse to a position in an active Window #post-3

                    Something like this would work:

                    const { parameter } = event.props
                    
                    const positionLookup = {
                        "High Frequency": { "x": 505, "y": 75 },
                        "High Gain": { "x": 555, "y": 75 },
                        "High Q": { "x": 605, "y": 75 },
                        "High-Mid Frequency": { "x": 505, "y": 75 },
                        "High-Mid Gain": { "x": 555, "y": 75 },
                        "High-Mid Q": { "x": 605, "y": 75 },
                        "Low-Mid Frequency": { "x": 505, "y": 145 },
                        "Low-Mid Gain": { "x": 555, "y": 145 },
                        "Low-Mid Q": { "x": 605, "y": 145 },
                        "Low Frequency": { "x": 505, "y": 175 },
                        "Low Gain": { "x": 555, "y": 175 },
                        "Low Q": { "x": 605, "y": 175 },
                        "Filter 1 Freq": { "x": 740, "y": 90 },
                        "Filter 2 Freq": { "x": 737, "y": 154 },
                    }
                    
                    // Lookup Selected Position
                    const selectedPosition = positionLookup[parameter];
                    
                    // Define anchor for relative position
                    const clipFXFrame = sf.ui.proTools.mainWindow.children.whoseRole.is("AXStaticText").whoseValue.is('Clip:').first.frame;
                    
                    // Set Mouse Position to Relative Position
                    sf.mouse.setPosition({
                        position: {
                            x: clipFXFrame.x + selectedPosition.x,
                            y: clipFXFrame.y + selectedPosition.y,
                        },
                    });
                    

                    This would be template script with one property called "parameter"



                    The generic knob command would look like



                    You could then change the parameter or "delta" value from the Mouse Scroll macro action in the Generic Knob Command to Modify Functions


                    1. Chad Wahlbrink @Chad2023-09-19 20:25:55.275Z

                      It's not quite as fluid as scrolling on a "mouse wheel" since most mouse drivers seem to react to some amount of "acceleration" and we are hardcoding a single "delta" value, but it's a bit more responsive overall for sure!

                      1. Awesome will experiment! If it really gains us nothing in fluidity I'll go back to the AXIncrement methodology. The up down arrows x10 fast was a bit smoother, but I don't think worth the unstablity.

                        1. Kitch Membery @Kitch2023-09-19 22:22:28.834Z

                          @Owen_Granich_Young

                          Fingers crossed the scrolling works well...

                          The following script will get you information on the sliders in the Clip Effects panel so you will no longer need the hard-coded lookup positions.

                          const clipEffectsPanel = sf.ui.proTools.mainWindow.groups.whoseTitle.is("Clip Effects").first;
                          
                          let clipGainSlidersInfo = clipEffectsPanel.children.filter(c => c.sliders).map(e => ({
                              element: e,
                              title: e.title.value,
                              value: e.value.intValue,
                              position: {
                                  x: e.position.x,
                                  y: e.position.y
                              },
                              centerPosition: {
                                  x: e.position.x + (e.frame.w / 2), // x position plus half the width of the element
                                  y: e.position.y + (e.frame.h / 2), // y position plus half the height of the element
                              },
                          }));
                          
                          log(clipGainSlidersInfo);
                          

                          Probably best to use the slider's center position for placing the mouse.

                          Rock on!

                          1. Awww man really?!?! I've spent all day on these look up tables LOLOLOL. This is more stable?

                            Yeah here's increment vs scroll wheel action, scroll wheel is a LOT more satisfying.
                            https://www.dropbox.com/scl/fi/mcdgbqphvg90z7ejryziu/scroll-wheel-delta-of-3-vs-AX-increment.mov?rlkey=qg4u9czst9sgc17p3ygb1n85d&dl=0

                            a delta of 3 on the scroll wheel seems to be the sweet spot

                            1. Kitch Membery @Kitch2023-09-19 22:29:38.519Z

                              Hahahaha Sorry!!! :-0

                          2. Kitch Membery @Kitch2023-09-19 22:25:11.251Z

                            Also, I think you and @Chad have exhausted the possibilities of controlling the AXIncrement AXDecrement, and Mouse scrolling techniques, and as Chad mentioned, it does not look like the slider values can be set directly. So I think you might be on the right track already.

                            1. Interestinly this breaks it:

                              One is making it so it hovers before scrolling but then it keeps 'rehovering' vs the other direction is just scroll wheel. I wonder if there's an 'if' statment that says 'yo if this is in the right place just ignore this part and scroll... Otherwise i'm just going to break it out into 'hover with knob kilck, and scroll only with the knobs'

                              1. Kitch Membery @Kitch2023-09-19 22:54:28.398Z

                                Is it only the reverse scroll that this is occurring with?

                                1. No, it's the fact that on the reverse scroll I've put the 'trigger the hover action first' so it needs some sort of 'is it in the right place, no go there' is it in the right place? Yes ok just scroll' but I couldn't quite figure out the right 'sf.mouse.getmousePosistion' build to make it do that move first then check...

                                  1. Kitch Membery @Kitch2023-09-19 22:58:30.052Z

                                    Try sf.mouse.getPosition().position

                                    1. BETA version for testing
                                      1. Chad Wahlbrink @Chad2023-09-20 13:14:24.496Z

                                        @Owen_Granich_Young, this is really great! The clicking functionality is interesting. I saw that code on the other thread.

                                        I did a bit of a refactor on your code for "Hover EQ Parameter Toggle" and tried using modifiers to change frequency, gain, and Q instead of the multiple clicks on the Stream Deck. Not sure which way is actually more intuitive yet, but interesting code all around!

                                        const { band } = event.props
                                        
                                        const positionLookupFreq = {
                                            "High Band": { "x": 505, "y": 75 },
                                            "High-Mid Band": { "x": 505, "y": 115 },
                                            "Low-Mid Band": { "x": 505, "y": 145 },
                                            "Low Band": { "x": 505, "y": 175 },
                                            "Filter 1": { "x": 740, "y": 90 },
                                            "Filter 2": { "x": 737, "y": 154 },
                                        }
                                        
                                        const positionLookupGain = {
                                            "High Band": { "x": 555, "y": 75 },
                                            "High-Mid Band": { "x": 555, "y": 115 },
                                            "Low-Mid Band": { "x": 555, "y": 145 },
                                            "Low Band": { "x": 555, "y": 175 },
                                        
                                        }
                                        const positionLookupQ = {
                                            "High Band": { "x": 605, "y": 75 },
                                            "High-Mid Band": { "x": 605, "y": 115 },
                                            "Low-Mid Band": { "x": 605, "y": 145 },
                                            "Low Band": { "x": 605, "y": 175 },
                                        }
                                        
                                        const clipEffectsPanel = sf.ui.proTools.windows.whoseTitle.startsWith('Edit: ').first.children.whoseTitle.is('Clip Effects');
                                        const clipFXisEnabled = sf.ui.proTools.getMenuItem("View", "Other Displays", "Clip Effects").isMenuChecked
                                        
                                        function showMeClipFX() {
                                            if (!clipFXisEnabled) {
                                                sf.ui.proTools.menuClick({
                                                    menuPath: ["View", "Other Displays", "Clip Effects"],
                                                    targetValue: "Enable",
                                                });
                                        
                                                sf.ui.proTools.mainWindow.buttons.whoseTitle.is('Compare').first.elementWaitFor();
                                        
                                                //Button
                                                var bypassBtn = sf.ui.proTools.mainWindow.buttons.whoseTitle.is('Effect Bypass').first;
                                        
                                                //Check if button is enabled
                                                if (bypassBtn.invalidate().value.value == "not equal")
                                                    log("Compare Button is enabled");
                                                if (bypassBtn.invalidate().value.value == "equal")
                                                    log("Compare Button is not enabled");
                                                if (bypassBtn.invalidate().value.value == "no selection")
                                                    log("⚠️ • No Full Audio Clip Selected • ⚠️");
                                            }
                                        }
                                        
                                        function hover(band, control) {
                                            const clipFXFrame = sf.ui.proTools.mainWindow.children.whoseRole.is("AXStaticText").whoseValue.is('Clip:').first.frame;
                                        
                                            // Set Mouse Position to Relative Position
                                            sf.mouse.setPosition({
                                                position: {
                                                    x: clipFXFrame.x + control[band].x,
                                                    y: clipFXFrame.y + control[band].y,
                                                },
                                            });
                                        
                                        }
                                        
                                        function filterOnCheck() {
                                            if (band.startsWith('Filter')) {
                                                if (clipEffectsPanel.first.children.whoseTitle.is(`${band} In/Out`).first.value.intValue == 0) {
                                                    clipEffectsPanel.first.children.whoseTitle.is(`${band} In/Out`).first.elementClick({ actionName: "AXIncrement" });
                                                }
                                            }
                                        }
                                        
                                        function main() {
                                            showMeClipFX()
                                            filterOnCheck()
                                        
                                            const kb = event.keyboardState;
                                            switch (true) {
                                                case (kb.hasCommand):
                                                    hover(band, positionLookupGain);
                                                    break;
                                                case (kb.hasAlt):
                                                    hover(band, positionLookupQ);
                                                    break;
                                                default:
                                                    hover(band, positionLookupFreq);
                                                    break;
                                            }
                                        };
                                        
                                        main();
                                        
                                    2. Chad Wahlbrink @Chad2023-09-20 13:26:25.766Z

                                      @Owen_Granich_Young, here's an example of checking the mouse position with an if statement

                                      onst { band } = event.props
                                      
                                      const positionLookupFreq = {
                                          "High Band": { "x": 505, "y": 75 },
                                          "High-Mid Band": { "x": 505, "y": 115 },
                                          "Low-Mid Band": { "x": 505, "y": 145 },
                                          "Low Band": { "x": 505, "y": 175 },
                                          "Filter 1": { "x": 740, "y": 90 },
                                          "Filter 2": { "x": 737, "y": 154 },
                                      }
                                      
                                      function hover(band, control) {
                                          const clipFXFrame = sf.ui.proTools.mainWindow.children.whoseRole.is("AXStaticText").whoseValue.is('Clip:').first.frame;
                                      
                                          const controlPosition = {
                                                  x: clipFXFrame.x + control[band].x,
                                                  y: clipFXFrame.y + control[band].y,
                                              }
                                          
                                          // Check Position of mouse
                                          if(sf.mouse.getPosition() != controlPosition ){
                                          // Set Mouse Position to Relative Position
                                          sf.mouse.setPosition({
                                              position: controlPosition,
                                          });
                                          }
                                      }
                                      
                                      hover(band, positionLookupFreq);
                                      
                                      1. Chad Wahlbrink @Chad2023-09-20 13:28:16.258Z

                                        Using that script as the "check for placement" template action in the generic knob command. Seems to work on my end!

                                        1. In reply toChad:
                                          Kitch Membery @Kitch2023-09-20 14:09:02.752Z

                                          Hi @Chad,

                                          I believe the way you are comparing the two objects will always return true.

                                          if(sf.mouse.getPosition() != controlPosition )
                                          

                                          Try instead using JSON.stringify to compare the two position objects.

                                          Something like this, untested pseudo code...

                                          const { band } = event.props;
                                          
                                          const positionLookupFreq = {
                                            "High Band": { x: 505, y: 75 },
                                            "High-Mid Band": { x: 505, y: 115 },
                                            "Low-Mid Band": { x: 505, y: 145 },
                                            "Low Band": { x: 505, y: 175 },
                                            "Filter 1": { x: 740, y: 90 },
                                            "Filter 2": { x: 737, y: 154 },
                                          };
                                          
                                          function comparePositions(pos1, pos2) {
                                            return JSON.stringify(pos1) === JSON.stringify(pos2);
                                          }
                                          
                                          function hover(band, control) {
                                            const clipFXFrame = sf.ui.proTools.mainWindow.children.whoseRole
                                              .is("AXStaticText")
                                              .whoseValue.is("Clip:").first.frame;
                                          
                                            const controlPosition = {
                                              x: clipFXFrame.x + control[band].x,
                                              y: clipFXFrame.y + control[band].y,
                                            };
                                          
                                            const currentPosition = sf.mouse.getPosition().position;
                                          
                                            // Check position of mouse  
                                            const isInPosition = comparePositions(currentPosition, controlPosition);
                                          
                                            if (!isInPosition) {
                                              // Set Mouse Position to Relative Position
                                              sf.mouse.setPosition({
                                                position: controlPosition,
                                              });
                                            }
                                          }
                                          
                                          hover(band, positionLookupFreq);
                                          

                                          Rock on!

                                          1. Chad Wahlbrink @Chad2023-09-20 14:32:44.791Z

                                            @Kitch is right! Of course, he is 👏👏

                                            This seems to work:

                                            const { band } = event.props
                                            
                                            const positionLookupFreq = {
                                                "High Band": { "x": 505, "y": 75 },
                                                "High-Mid Band": { "x": 505, "y": 115 },
                                                "Low-Mid Band": { "x": 505, "y": 145 },
                                                "Low Band": { "x": 505, "y": 175 },
                                                "Filter 1": { "x": 740, "y": 90 },
                                                "Filter 2": { "x": 737, "y": 154 },
                                            }
                                            
                                            // Compare String of object 1 and object 2
                                            function comparePositions(pos1, pos2) {
                                                return JSON.stringify(pos1) === JSON.stringify(pos2);
                                            }
                                            
                                            function hover(band, control) {
                                                // Define Clip FX Frame
                                                const clipFXFrame = sf.ui.proTools.mainWindow.children.whoseRole
                                                .is("AXStaticText")
                                                .whoseValue.is('Clip:').first.frame;
                                            
                                                // Lookup control position
                                                const controlPosition = {
                                                    x: clipFXFrame.x + control[band].x,
                                                    y: clipFXFrame.y + control[band].y,
                                                }
                                                
                                                // Get current Position Coords
                                                const currentPositionCoords = sf.mouse.getPosition().position;
                                            
                                                // Map the Coords to a new Position Object
                                                const currentPosition = {
                                                    x: currentPositionCoords.x,
                                                    y: currentPositionCoords.y,
                                                }
                                            
                                                // Check position of mouse  
                                                const isInPosition = comparePositions(currentPosition, controlPosition);
                                            
                                                if (!isInPosition) {
                                                    // Set Mouse Position to Relative Position
                                                    sf.mouse.setPosition({
                                                        position: controlPosition,
                                                    });
                                                }
                                            }
                                            
                                            hover(band, positionLookupFreq);
                                            
                                            1. Cool guys! I'll play around with this version and then post it to the package. I like the hold modifier one if I switch the Streamdeck+ to sit on my mouse side just above my mouse. if this works as intended would be really easy to have a 4 band on knobs. Filter 1 Low Band High Band Filter 2 spread across the 4 knobs and you're good to party. Gonna build the change filter mode template today, knob click will switch type.

                                              1. @Chad this 'check if the mouse is in the right place thing doesn't seem to be working on my end... here's a video of it set with the script ON the knob followed by scroll wheel (as your above snapshot) and then one of JUST scroll wheel. You can see how different the action is. Does it almost need an if/else with the scroll wheel macro built in?

                                                So IF we're in the wrong place, move to the right place, if not just scroll and stop checking to slow us down? Kinda why I was unable to wrap my head around the order of operations.

                                                I really do want to be able to just twist the Knob and it goes not having to press a button to float would be great, but also it's gotta be as fast as the pure scroll wheel mode or what's the point.

                                                1. Chad Wahlbrink @Chad2023-09-20 21:37:44.397Z

                                                  Hey @Owen_Granich_Young!

                                                  Check out this demo video of where I got it on my end:
                                                  https://www.dropbox.com/scl/fi/cxxnsawf7sjznybw2qkzq/2023-09-20-Clip-FX-Knobx.mp4?rlkey=azwnv1kzl0ephjy2tyqt7e2df&dl=0

                                                  I think the biggest gain was removing:

                                                  showMeClipFX()
                                                  filterOnCheck()
                                                  

                                                  from the scrolling scripts.

                                                  1. Chad Wahlbrink @Chad2023-09-20 21:42:56.478Z

                                                    Clip Effects Control (Scrolling)

                                                    if (!sf.ui.proTools.isRunning) throw `Pro Tools is not running`;
                                                    
                                                    const { band, scrollDelta } = event.props
                                                    
                                                    const positionLookupFreq = {
                                                        "EQ High": { "x": 505, "y": 75 },
                                                        "EQ High-Mid": { "x": 505, "y": 115 },
                                                        "EQ Low-Mid": { "x": 505, "y": 145 },
                                                        "EQ Low": { "x": 505, "y": 175 },
                                                        "Filter 1": { "x": 730, "y": 85 },
                                                        "Filter 2": { "x": 730, "y": 154 },
                                                    }
                                                    
                                                    const positionLookupGain = {
                                                        "EQ High": { "x": 555, "y": 75 },
                                                        "EQ High-Mid": { "x": 555, "y": 115 },
                                                        "EQ Low-Mid": { "x": 555, "y": 145 },
                                                        "EQ Low": { "x": 555, "y": 175 },
                                                    
                                                    }
                                                    const positionLookupQ = {
                                                        "EQ High": { "x": 605, "y": 75 },
                                                        "EQ High-Mid": { "x": 605, "y": 115 },
                                                        "EQ Low-Mid": { "x": 605, "y": 145 },
                                                        "EQ Low": { "x": 605, "y": 175 },
                                                    }
                                                    
                                                    // Compare String of object 1 and object 2
                                                    function comparePositions(pos1, pos2) {
                                                        return JSON.stringify(pos1) === JSON.stringify(pos2);
                                                    }
                                                    
                                                    function hover(band, control) {
                                                        // Define Clip FX Frame
                                                        const clipFXFrame = sf.ui.proTools.mainWindow.children.whoseRole
                                                            .is("AXStaticText")
                                                            .whoseValue.is('Clip:').first.frame;
                                                    
                                                        // Lookup control position
                                                        const controlPosition = {
                                                            x: clipFXFrame.x + control[band].x,
                                                            y: clipFXFrame.y + control[band].y,
                                                        }
                                                    
                                                        // Get current Position Coords
                                                        const currentPositionCoords = sf.mouse.getPosition().position;
                                                    
                                                        // Map the Coords to a new Position Object
                                                        const currentPosition = {
                                                            x: currentPositionCoords.x,
                                                            y: currentPositionCoords.y,
                                                        }
                                                    
                                                        // Check position of mouse  
                                                        const isInPosition = comparePositions(currentPosition, controlPosition);
                                                    
                                                        if (!isInPosition) {
                                                            // Set Mouse Position to Relative Position
                                                            sf.mouse.setPosition({
                                                                position: controlPosition,
                                                            });
                                                        }
                                                    }
                                                    
                                                    function main() {
                                                        const kb = event.keyboardState;
                                                        switch (true) {
                                                            case (kb.hasCommand):
                                                                if (positionLookupGain[band]) {
                                                                    hover(band, positionLookupGain);
                                                                    sf.mouse.scroll({ delta: Number(scrollDelta) });
                                                                }
                                                                break;
                                                            case (kb.hasAlt):
                                                                if (positionLookupQ[band]) {
                                                                    hover(band, positionLookupQ);
                                                                    sf.mouse.scroll({ delta: Number(scrollDelta) * 3 });
                                                                }
                                                                break;
                                                            case (kb.hasShift):
                                                                hover(band, positionLookupFreq);
                                                                sf.mouse.scroll({ delta: Number(scrollDelta)});
                                                                break;
                                                            default:
                                                                hover(band, positionLookupFreq);
                                                                sf.mouse.scroll({ delta: Number(scrollDelta) * 3 });
                                                                break;
                                                        }
                                                    };
                                                    
                                                    main();
                                                    
                                                    1. In reply toChad:
                                                      Chad Wahlbrink @Chad2023-09-20 21:43:54.135Z2023-09-20 21:57:17.639Z

                                                      Clip Effects Buttons (In/Out)

                                                      if (!sf.ui.proTools.isRunning) throw `Pro Tools is not running`;
                                                      
                                                      const { band, } = event.props;
                                                      
                                                      const clipEffectsPanel = sf.ui.proTools.windows.whoseTitle.startsWith('Edit: ').first.children.whoseTitle.is('Clip Effects');
                                                      const clipFXisEnabled = sf.ui.proTools.getMenuItem("View", "Other Displays", "Clip Effects").isMenuChecked
                                                      
                                                      function showMeClipFX() {
                                                          if (!clipFXisEnabled) {
                                                              sf.ui.proTools.menuClick({
                                                                  menuPath: ["View", "Other Displays", "Clip Effects"],
                                                                  targetValue: "Enable",
                                                              });
                                                      
                                                              sf.ui.proTools.mainWindow.buttons.whoseTitle.is('Compare').first.elementWaitFor();
                                                      
                                                              //Button
                                                              var bypassBtn = sf.ui.proTools.mainWindow.buttons.whoseTitle.is('Effect Bypass').first;
                                                      
                                                              //Check if button is enabled
                                                              if (bypassBtn.invalidate().value.value == "not equal")
                                                                  log("Compare Button is enabled");
                                                              if (bypassBtn.invalidate().value.value == "equal")
                                                                  log("Compare Button is not enabled");
                                                              if (bypassBtn.invalidate().value.value == "no selection")
                                                                  log("⚠️ • No Full Audio Clip Selected • ⚠️");
                                                          }
                                                      }
                                                      
                                                      function inOut() {
                                                              if (clipEffectsPanel.first.children.whoseTitle.is(`${band} In/Out`).first.value.intValue == 0) {
                                                                  clipEffectsPanel.first.children.whoseTitle.is(`${band} In/Out`).first.elementClick({ actionName: "AXIncrement" });
                                                              }
                                                              else{
                                                                  clipEffectsPanel.first.children.whoseTitle.is(`${band} In/Out`).first.elementClick({ actionName: "AXDecrement" });
                                                              }
                                                      }
                                                      
                                                      function main() {
                                                          showMeClipFX()
                                                          inOut()
                                                      };
                                                      
                                                      main();
                                                      
                                                      1. In reply toChad:
                                                        Chad Wahlbrink @Chad2023-09-20 21:44:49.011Z

                                                        Generic Knob Commands Example

                                                        1. Masterful Work Chad! All makes sense to me! Will play around on my end and upload for ppl to explore.

                                                          I also made the AXIncrement version today for fun. It's nice cuz you can just spread a single band's parameters across 3 wheels, it's not so nice because it's slow.

                                                          ps... how would you toggle through 4 filter types?

                                                          Toggling the HF and LF is easy via

                                                          const  {eqType} = event.props
                                                          
                                                          let clipEffectsPanel = sf.ui.proTools.windows.whoseTitle.startsWith('Edit: ').first.children.whoseTitle.is('Clip Effects');
                                                          
                                                          if(clipEffectsPanel.first.children.whoseTitle.is(eqType).first.value.intValue == 0){
                                                              clipEffectsPanel.first.children.whoseTitle.is(eqType).first.elementClick({actionName:"AXIncrement"});
                                                          } else{
                                                              clipEffectsPanel.first.children.whoseTitle.is(eqType).first.elementClick({actionName:"AXDecrement"});
                                                          }
                                                          

                                                          But it breaks on the filters which have 4 options.. so you either want it to reach 3 and start back at 1 or reach three and incremnt back down.... little bit of a syntax head scratcher for me.

                                                          1. Chad Wahlbrink @Chad2023-09-20 22:10:06.039Z

                                                            It's tricky because you can't "set" the value. This will work for EQ High, EQ Low, and the Filters, though.

                                                            if (!sf.ui.proTools.isRunning) throw `Pro Tools is not running`;
                                                            
                                                            const { band, } = event.props;
                                                            
                                                            const clipEffectsPanel = sf.ui.proTools.windows.whoseTitle.startsWith('Edit: ').first.children.whoseTitle.is('Clip Effects');
                                                            const clipFXisEnabled = sf.ui.proTools.getMenuItem("View", "Other Displays", "Clip Effects").isMenuChecked
                                                            
                                                            function showMeClipFX() {
                                                                if (!clipFXisEnabled) {
                                                                    sf.ui.proTools.menuClick({
                                                                        menuPath: ["View", "Other Displays", "Clip Effects"],
                                                                        targetValue: "Enable",
                                                                    });
                                                            
                                                                    sf.ui.proTools.mainWindow.buttons.whoseTitle.is('Compare').first.elementWaitFor();
                                                            
                                                                    //Button
                                                                    var bypassBtn = sf.ui.proTools.mainWindow.buttons.whoseTitle.is('Effect Bypass').first;
                                                            
                                                                    //Check if button is enabled
                                                                    if (bypassBtn.invalidate().value.value == "not equal")
                                                                        log("Compare Button is enabled");
                                                                    if (bypassBtn.invalidate().value.value == "equal")
                                                                        log("Compare Button is not enabled");
                                                                    if (bypassBtn.invalidate().value.value == "no selection")
                                                                        log("⚠️ • No Full Audio Clip Selected • ⚠️");
                                                                }
                                                            }
                                                            
                                                            function switchFilters() {
                                                                if (band.startsWith('Filter') && clipEffectsPanel.first.children.whoseTitle.is(`${band} Type`).first.value.intValue < 3) {
                                                                    clipEffectsPanel.first.children.whoseTitle.is(`${band} Type`).first.elementClick({ actionName: "AXIncrement" });
                                                                } else if (band.startsWith('Filter') && clipEffectsPanel.first.children.whoseTitle.is(`${band} Type`).first.value.intValue === 3){
                                                                    clipEffectsPanel.first.children.whoseTitle.is(`${band} Type`).first.elementClick({ actionName: "AXDecrement" });
                                                                    clipEffectsPanel.first.children.whoseTitle.is(`${band} Type`).first.elementClick({ actionName: "AXDecrement" });
                                                                    clipEffectsPanel.first.children.whoseTitle.is(`${band} Type`).first.elementClick({ actionName: "AXDecrement" });
                                                                } else if ((band === 'EQ High' || band === 'EQ Low' ) && clipEffectsPanel.first.children.whoseTitle.is(`${band} Type`).first.value.intValue === 0){
                                                                    clipEffectsPanel.first.children.whoseTitle.is(`${band} Type`).first.elementClick({ actionName: "AXIncrement" });
                                                                } else if ((band === 'EQ High' || band === 'EQ Low' ) && clipEffectsPanel.first.children.whoseTitle.is(`${band} Type`).first.value.intValue === 1){
                                                                    clipEffectsPanel.first.children.whoseTitle.is(`${band} Type`).first.elementClick({ actionName: "AXDecrement" });
                                                                }
                                                            }
                                                            
                                                            function main() {
                                                                showMeClipFX()
                                                                switchFilters()
                                                            };
                                                            
                                                            main();
                                                            
                                                            1. I THINK THIIS THE ONE! Don't hate me I'm going to change which modifier keys do what... personal preference. Don't feel like making it user defined in template only to find out that slows the script down, but if it doesn't that'd be a nice sweetener.

                                                              1. Chad Wahlbrink @Chad2023-09-20 23:41:36.990Z

                                                                Love it! Do as you please. I have my own versions haha

                                                                I don’t think making modifiers customizable would knock performance, but not essential for getting it out there!

                                                                1. New Version is up. All credit to @Chad

                                                                  1. You guys beat me to it!

                                                                    1. HAVE YOU BEEN WORKING ON THIS?! This is not Audiosuites (which i'm still waiting for btw), and I can't figure out how to get it on the Midi Fighter Twister knobs.

                                                                      1. In reply toChris_Shaw:
                                                                        Chad Wahlbrink @Chad2023-09-21 02:24:52.108Z

                                                                        Yeah… if only I could use this with a midi fighter twister 😉