No internet connection
  1. Home
  2. How to

Controlling iZotope RX sliders with MIDI faders and knobs

By Vladimir @poterukha
    2019-12-27 13:59:23.037Z

    Is there a way to move sliders in iZotope RX with MIDI faders and knobs? The example of the script, which we have in the Dialogue editing package, is designed to control the slider with two triggers. I.e you need to assign triggers separately for increment and decrement. How to control it with a single MIDI trigger? I mean, if the velocity of MIDI-trigger is increasing the slider's value is also increasing and vice versa.

    Solved in post #20, click to view
    • 37 replies

    There are 37 replies. Estimated reading time: 41 minutes

    1. You can use the event.trigger.midiBytes array to filter on incoming MIDI input.
      There's a good discussion here on how to use that array to filter together with relative MIDI knobs, and using if blocks to then determine what to do:
      https://forum.soundflow.org/-1407/creating-macros-for-audiomidi-clip-movement-in-nuendocubase-with-midi-knobs

      1. In reply topoterukha:
        Vladimir @poterukha
          2020-01-02 17:24:38.003Z2020-01-02 18:27:37.108Z

          Thanks, it is a very helpful thread. There are 2 really useful examples for mid-position of pots/faders and for vpots. In my mind, for triggering with faders/pots it will be better to have another solution. Is it possible to modify a logical part of the script to make SF record each time the current velocity value? Then if m2 increases on the trigger SF executes setSliderValueDelta(1, 0.2) if m2 decreases SF executes setSliderValueDelta(1, -0.2). There are its own cons in this scenario, but for most of the positions of fader/pots, it will work fine.

          function setSliderValueDelta(sliderNum, deltaValue) {
          
              var moduleWin = sf.ui.izotope.floatingWindows.whoseDocument.isNullOrEmpty.first;
              if (!moduleWin.exists) throw 'Module not found';
          
              var panel = moduleWin.groups.whoseDescription.contains('EffectPanel').first;
              if (!panel.exists) throw 'Could not find EffectPanel';
          
              var slider = panel.children.allItems.filter(function (c) { return c.role == 'AXSlider' })[sliderNum - 1];
              if (!slider || !slider.exists) throw 'Could not find slider';
          
              var intDelta = Math.floor(deltaValue * 10);
              var absDelta = Math.abs(intDelta);
          
              if (intDelta > 0) {
                  for (var i = 0; i < absDelta; i++)
                      slider.elementClick({ actionName: 'AXIncrement' });
              } else if (intDelta < 0) {
                  for (var i = 0; i < absDelta; i++)
                      slider.elementClick({ actionName: 'AXDecrement' });
              }
          }
          
          var m = event.trigger.midiBytes;
          var m0 = m[0], m1 = m[1], m2 = m[2];
          
          //log(`m0: ${m0}, m1: ${m1}, m2: ${m2}`);
          
          if (m0 == 176 && m1 == 16 && m2 == 0) {
              //Fader down
              setSliderValueDelta(1, 0.2)
                  ;
          }
          else if (m0 == 176 && m1 == 16 && m2 == 127) {
              //Fader up
              setSliderValueDelta(1, -0.2)
              ;
          
          }
          
          1. Christian Scheuer @chrscheuer2020-01-02 18:32:44.441Z2020-01-02 21:59:36.651Z

            Great progress!

            Is it possible to modify a logical part of the script to make SF record each time the current velocity value

            Yes we could do this. Right now it looks like you're using an absolute MIDI knob and then your script is only reacting once you hit the boundaries (minimum and maximum value, respectively).
            Don't you have the possibility of using relative MIDI knobs - or is this not supported on your MIDI device? They would give you the best experience.

            If you need to work with absolute knobs, and then compute the relative change yourself, you could save the last value of m2 in a global state variable, and then react accordingly:

            
            var m = event.trigger.midiBytes;
            var m0 = m[0], m1 = m[1], m2 = m[2];
            
            if (m0 == 176 && m1 == 16) {
                //Get the last recorded slider value
                var lastValue = globalState.lastSliderValue;
            
                if (m2 > lastValue) {
                    //The new value is larger than the last value, ie. positive delta
            
                    //Fader up
                    setSliderValueDelta(1, -0.2);
                } else if (m2 < lastValue) {
                    //The new value is smaller than the last value, ie. negative delta
            
                    //Fader down
                    setSliderValueDelta(1, 0.2);
                }
            
                //Now store the current value as the last recorded slider value
                globalState.lastSliderValue = m2;
            }
            
            
            1. Vladimir @poterukha
                2020-01-02 21:48:48.297Z

                Thank you, it works fine!
                Yes, I have Korg nanoKontrol 2 and it has 8 faders, 8 pots, but it doesn't have relative knobs.
                Now I realized that not all the modules in iZotope RX 7 are controlled by sliders. For example, controls in Dialogue Isolate and Leveler look like sliders but aren't controlling by setSliderValueDelta function. Also, the maximum number of sliders I can get is 8 at the Spectral Denoise module, but when the advanced option is on it reacts on the last slider strangely. https://yadi.sk/i/sZjhVrXBSQwzkw

                1. That looks cool :)
                  Was the video meant to illustrate an issue or when it works? It looks to be working pretty well already!
                  Now imagine that you use an iPad to control this... with a SoundFlow designed surface.... ;)

                  1. Note I just edited the script above to become a little simpler.

                    1. In reply tochrscheuer:
                      Vladimir @poterukha
                        2020-01-02 22:09:39.612Z

                        I am sorry for a mess, I forgot that the Algorithm position is changing some parameters in advanced option. I just can't get more than 8 sliders at any module.
                        Yes, an iPad surface will be perfect. I hope we will see it soon.

                        1. I just can't get more than 8 sliders at any module.

                          My advice:
                          Make sure to debug any issues independently of the MIDI logic, that should help isolate any inconsistensies.
                          I'm sure iZotope's UI mappings are not entirely logical. But you may also have differences in the MIDI being sent. Debugging both at the same time could make things more complex than they need to be.

                          1. Vladimir @poterukha
                              2020-01-02 22:26:25.856Z

                              function setSliderValueDelta(sliderNum, deltaValue) {

                              var moduleWin = sf.ui.izotope.floatingWindows.whoseDocument.isNullOrEmpty.first;
                              if (!moduleWin.exists) throw 'Module not found';
                              
                              var panel = moduleWin.groups.whoseDescription.contains('EffectPanel').first;
                              if (!panel.exists) throw 'Could not find EffectPanel';
                              
                              var slider = panel.children.allItems.filter(function(c){ return c.role == 'AXSlider' })[sliderNum - 1];
                              if (!slider || !slider.exists) throw 'Could not find slider';
                              
                              var intDelta = Math.floor(deltaValue * 10);
                              var absDelta = Math.abs(intDelta);
                              
                              if (intDelta > 0) {
                                  for (var i = 0; i < absDelta; i++)
                                      slider.elementClick({ actionName: 'AXIncrement' });
                              } else if (intDelta < 0) {
                                  for (var i = 0; i < absDelta; i++)
                                      slider.elementClick({ actionName: 'AXDecrement' });
                              }
                              

                              setSliderValueDelta(9, 0.2);

                              I simplified the script just to get the 9th slider and SF log says that it is couldn't find it.

                              Slider 9.rtf (2.03 kB)

                              1. It's likely that you need a different path to that slider.
                                Best approach would be to use the UI picker in a macro to figure out its path and then copy that to Javascript.
                                Have you seen the videos showcasing this approach in the user group?

                                1. Vladimir @poterukha
                                    2020-01-02 22:33:26.444Z

                                    You are doing a lot of stuff for us and maybe I missed it. I will check the paths for advanced guys and another controls tomorrow.

                                    1. In reply tochrscheuer:
                                      Vladimir @poterukha
                                        2020-01-03 17:14:18.275Z

                                        I tried to pick up UI elements in iZotope RX and it is a bit messy. For example, in Spectral De-noise module one of the sliders called Suppression Linked or 2nd Slider. If the Reduction and the Threshold linked, Reduction is controlling with slider 2. If the Threshold and the Reduction are unlinked they are occupying the first 4 sliders, so the number for next sliders is related to the Threshold/Reduction linking state. In the Dialogue Isolate module the right slider called Noise gain [dB] or 2nd Slider and it isn't controlling with any slider number at setSliderValueDelta(2, 0.2). I have tried from 1 to 16. Also, I can't make it work for many modules – Dialogue Isolate, Dialogue De-reverb, De-rustle, Leveler, Breath control, Music Rebalance – and can't see any patterns why it happens. I thought that it somehow connected with the new modules from RX 6 or RX 7, but it perfectly works with De-wind.

                                        1. Yes it's indeed very messy the way iZotope lays out its UI. It's generally very inconsequential.
                                          You shouldn't be looking too much at setSliderValueDelta as that was just a function modelled on how some sliders can be approached but as you've seen obviously does not cover all cases.

                                          The annoying part of all this complexity in their UI?
                                          Your script will have to work around it.

                                          I don't know if you watched the videos yet?

                                          The general workflow would be:

                                          • In a macro, add an "Click UI Element" action and use the UI Picker to find a slider.
                                          • If you can find a path by titles (not indexed, like 2nd Slider) that's preferable.
                                          • Use the drop downs in the picker to figure this out (will make sense if you've seen the videos).
                                          • Once you have the best possible path, copy that to Javascript via the three small dots.

                                          That code will then look something like this (if you selected the linked one):

                                          sf.ui.izotope.windows.whoseTitle.is('Spectral De-noise').first.groups.whoseDescription.is('EffectPanel Spectral Denoise').first.sliders.whoseDescription.is('Suppression Linked').first.elementClick();
                                          

                                          Now to make this work in a function, all you'd need to do would be to use this new version of the setSliderValueDelta function that now accepts a UI element instead of a number.

                                          
                                          function setSliderValueDelta(slider, deltaValue) {
                                              var intDelta = Math.floor(deltaValue * 10);
                                              var absDelta = Math.abs(intDelta);
                                          
                                              if (intDelta > 0) {
                                                  for (var i = 0; i < absDelta; i++)
                                                      slider.elementClick({ actionName: 'AXIncrement' });
                                              } else if (intDelta < 0) {
                                                  for (var i = 0; i < absDelta; i++)
                                                      slider.elementClick({ actionName: 'AXDecrement' });
                                              }
                                          }
                                          
                                          setSliderValueDelta(
                                              sf.ui.izotope.windows.whoseTitle.is('Spectral De-noise').first.groups.whoseDescription.is('EffectPanel Spectral Denoise').first.sliders.whoseDescription.is('Suppression Linked').first,
                                              0.1
                                          );
                                          

                                          Note how I used the path I got from the Javascript and just removed .elementClick()?

                                          Now let's say you want to make this work also when unlinked, and then it should control just the noise slider.

                                          Then you'd make a separate function for getting the best possible slider:

                                          
                                          function setSliderValueDelta(slider, deltaValue) {
                                              var intDelta = Math.floor(deltaValue * 10);
                                              var absDelta = Math.abs(intDelta);
                                          
                                              if (intDelta > 0) {
                                                  for (var i = 0; i < absDelta; i++)
                                                      slider.elementClick({ actionName: 'AXIncrement' });
                                              } else if (intDelta < 0) {
                                                  for (var i = 0; i < absDelta; i++)
                                                      slider.elementClick({ actionName: 'AXDecrement' });
                                              }
                                          }
                                          
                                          function getLinkedOrNoiseSlider() {
                                              //First try to use the linked slider
                                              var linkedSlider = sf.ui.izotope.windows.whoseTitle.is('Spectral De-noise').first.groups.whoseDescription.is('EffectPanel Spectral Denoise').first.sliders.whoseDescription.is('Suppression Linked').first;
                                              if (linkedSlider.exists)
                                                  return linkedSlider;
                                          
                                              //Then try to use the noisy slider
                                              var noisySlider = sf.ui.izotope.windows.whoseTitle.is('Spectral De-noise').first.groups.whoseDescription.is('EffectPanel Spectral Denoise').first.sliders.whoseDescription.is('Noisy').first;
                                              if (noisySlider.exists)
                                                  return noisySlider;
                                          
                                              throw "Could not find either the linked slider or the noisy slider";
                                          }
                                          
                                          setSliderValueDelta(
                                              getLinkedOrNoiseSlider(),
                                              2
                                          );
                                          
                                          1. Vladimir @poterukha
                                              2020-01-03 18:24:49.048Z

                                              Thanks, I understood. So sad that such elegant and universal solution with just number at var slider = panel.children.allItems.filter(function (c) { return c.role == 'AXSlider' })[sliderNum - 1]; doesn't work for all modules.

                                              1. Yea - well if you gather a collection of paths like these:

                                                sf.ui.izotope.windows.whoseTitle.is('Spectral De-noise').first.groups.whoseDescription.is('EffectPanel Spectral Denoise').first.sliders.whoseDescription.is('Noisy').first
                                                

                                                .. then we might be able to find a pattern that can allow us to write some even more elegant code.

                                                What's the overall functionality you're looking for btw?
                                                We can likely make a more elegant solution for everything if I understand better how you intend your physical sliders to be mapped to the virtual ones.

                                                1. Vladimir @poterukha
                                                    2020-01-03 19:20:30.759Z

                                                    The original idea was to create a universal surface/controller to manage a massive amount of sliders in RX without making kilometers with a mouse. Maybe, the perfect solution is an iPad surface, but Korg nanoKontrol 2 is also suitable. Because it is 8 faders - 8 pots unit, you can control any module with it. In my dialogue restoration workflow, I prefer to keep more or less all modules opened on the second screen. I set up this simple kind of script sf.ui.izotope.windows.whoseTitle.is('Module Name').first.elementRaise(); on Solo buttons to focus a specific module I need to control. Mute buttons I use for Preview, Rec is for Render. If all modules were following numbers of sliders in the same way, it will be so simple just to raise a module you need, adjust parameters, preview, and render the result. In the single MIDI layout with no need to change CC values.

                                                    1. Ah ok gotcha.

                                                      It should be possible to make a script that handles all sliders in each module regardless of their path.
                                                      Let me check.

                                                      1. This script seems to work well for me:

                                                        /* CONFIG */
                                                        
                                                        const faderCCs = {
                                                            12: 1,
                                                            13: 2,
                                                            14: 3,
                                                            16: 4,
                                                            17: 5,
                                                            18: 6,
                                                            19: 7,
                                                            20: 8,
                                                        };
                                                        
                                                        /* SCRIPT */
                                                        
                                                        
                                                        function getAllSliders(element, elements) {
                                                            if (element.role == "AXSlider") elements.push(element);
                                                            else if (element.fullRole == "AXGroup" || element.role == "AXWindow")
                                                                element.children.allItems.map(c => getAllSliders(c, elements));
                                                        }
                                                        
                                                        function setSliderValueDelta(sliderNum, deltaValue) {
                                                        
                                                            var moduleWin = sf.ui.izotope.floatingWindows.invalidate().whoseDocument.isNullOrEmpty.first;
                                                            if (!moduleWin.exists) throw 'Module not found';
                                                        
                                                            var allSliders = [];
                                                            getAllSliders(moduleWin, allSliders);
                                                        
                                                            if (allSliders.length === 0) {
                                                                throw 'No sliders in this window: ' + moduleWin.title.value;
                                                            }
                                                        
                                                            var slider = allSliders[sliderNum - 1];
                                                            if (!slider || !slider.exists) throw 'Could not find slider';
                                                        
                                                            var intDelta = Math.floor(deltaValue * 10);
                                                            var absDelta = Math.abs(intDelta);
                                                        
                                                            if (intDelta > 0) {
                                                                for (var i = 0; i < absDelta; i++)
                                                                    slider.elementClick({ actionName: 'AXIncrement' });
                                                            } else if (intDelta < 0) {
                                                                for (var i = 0; i < absDelta; i++)
                                                                    slider.elementClick({ actionName: 'AXDecrement' });
                                                            }
                                                        }
                                                        
                                                        function main() {
                                                        
                                                            var m = event.trigger.midiBytes;
                                                            var m0 = m[0], m1 = m[1], m2 = m[2];
                                                        
                                                            var faderNum = faderCCs[m1];
                                                        
                                                            if (m0 == 176 && faderNum !== undefined) {
                                                                //Get the last recorded slider value
                                                                var lastValue = globalState['lastSliderValue_' + faderNum];
                                                        
                                                                if (m2 > lastValue) {
                                                                    //The new value is larger than the last value, ie. positive delta
                                                        
                                                                    //Fader up
                                                                    setSliderValueDelta(faderNum, 0.2);
                                                                } else if (m2 < lastValue) {
                                                                    //The new value is smaller than the last value, ie. negative delta
                                                        
                                                                    //Fader down
                                                                    setSliderValueDelta(faderNum, -0.2);
                                                                }
                                                        
                                                                //Now store the current value as the last recorded slider value
                                                                globalState['lastSliderValue_' + faderNum] = m2;
                                                            }
                                                        
                                                        
                                                            if (m0 == 176 && m1 == 16 && m2 == 0) {
                                                                //Fader down
                                                                setSliderValueDelta(1, 0.2);
                                                            }
                                                            else if (m0 == 176 && m1 == 16 && m2 == 127) {
                                                                //Fader up
                                                                setSliderValueDelta(1, -0.2);
                                                            }
                                                        }
                                                        
                                                        main();
                                                        

                                                        Note how you configure the CC num -> fader mapping in the top?

                                                        1. And here the script has evolved to using caching to make it snappier

                                                          /* CONFIG */
                                                          
                                                          const faderCCs = {
                                                              12: 1,
                                                              13: 2,
                                                              14: 3,
                                                              16: 4,
                                                              17: 5,
                                                              18: 6,
                                                              19: 7,
                                                              20: 8,
                                                          };
                                                          
                                                          /* SCRIPT */
                                                          
                                                          
                                                          function getAllSliders(element, elements) {
                                                              if (element.role == "AXSlider") elements.push(element);
                                                              else if (element.fullRole == "AXGroup" || element.role == "AXWindow")
                                                                  element.children.allItems.map(c => getAllSliders(c, elements));
                                                          }
                                                          
                                                          function getNumberedSlider(moduleWin, sliderNum) {
                                                              var moduleTitle = moduleWin.title.value;
                                                              var key = 'numberedSliders_' + moduleTitle + '_' + sliderNum;
                                                              var cachedSlider = globalState[key];
                                                              if (cachedSlider) return cachedSlider;
                                                          
                                                              var allSliders = [];
                                                              getAllSliders(moduleWin, allSliders);
                                                          
                                                              if (allSliders.length === 0) {
                                                                  throw 'No sliders in this window: ' + moduleWin.title.value;
                                                              }
                                                          
                                                              var slider = allSliders[sliderNum - 1];
                                                              if (slider && slider.exists) {
                                                                  globalState[key] = slider;
                                                                  return slider;
                                                              }
                                                          }
                                                          
                                                          function setSliderValueDelta(sliderNum, deltaValue) {
                                                          
                                                              var moduleWin = sf.ui.izotope.floatingWindows.invalidate().whoseDocument.isNullOrEmpty.first;
                                                              if (!moduleWin.exists) throw 'Module not found';
                                                          
                                                              var slider = getNumberedSlider(moduleWin, sliderNum);
                                                              if (!slider || !slider.exists) throw 'Could not find slider';
                                                          
                                                              var intDelta = Math.floor(deltaValue * 10);
                                                              var absDelta = Math.abs(intDelta);
                                                          
                                                              if (intDelta > 0) {
                                                                  for (var i = 0; i < absDelta; i++)
                                                                      slider.elementClick({ actionName: 'AXIncrement' });
                                                              } else if (intDelta < 0) {
                                                                  for (var i = 0; i < absDelta; i++)
                                                                      slider.elementClick({ actionName: 'AXDecrement' });
                                                              }
                                                          }
                                                          
                                                          function main() {
                                                          
                                                              var m = event.trigger.midiBytes;
                                                              var m0 = m[0], m1 = m[1], m2 = m[2];
                                                          
                                                              var faderNum = faderCCs[m1];
                                                          
                                                              if (m0 == 176 && faderNum !== undefined) {
                                                                  //Get the last recorded slider value
                                                                  var lastValue = globalState['lastSliderValue_' + faderNum];
                                                          
                                                                  if (m2 > lastValue) {
                                                                      //The new value is larger than the last value, ie. positive delta
                                                          
                                                                      //Fader up
                                                                      setSliderValueDelta(faderNum, 0.2);
                                                                  } else if (m2 < lastValue) {
                                                                      //The new value is smaller than the last value, ie. negative delta
                                                          
                                                                      //Fader down
                                                                      setSliderValueDelta(faderNum, -0.2);
                                                                  }
                                                          
                                                                  //Now store the current value as the last recorded slider value
                                                                  globalState['lastSliderValue_' + faderNum] = m2;
                                                              }
                                                          
                                                          
                                                              if (m0 == 176 && m1 == 16 && m2 == 0) {
                                                                  //Fader down
                                                                  setSliderValueDelta(1, 0.2);
                                                              }
                                                              else if (m0 == 176 && m1 == 16 && m2 == 127) {
                                                                  //Fader up
                                                                  setSliderValueDelta(1, -0.2);
                                                              }
                                                          }
                                                          
                                                          main();
                                                          
                                                          ReplySolution
                                                          1. Note this script is designed to work on all faders so you'll only have to have one script regardless of the number of faders.

                                                            1. In reply tochrscheuer:
                                                              Vladimir @poterukha
                                                                2020-01-05 13:29:13.186Z

                                                                Thanks a lot! It is working for me as well.
                                                                Here is example of the script I use to focus on modules:

                                                                var moduleName = "Spectral De-noise"
                                                                var moduleWin = sf.ui.izotope.windows.whoseTitle.is(moduleName);

                                                                if (!moduleWin.exists) sf.ui.izotope.menuClick({
                                                                menuPath: ['Modules', moduleName + '...']
                                                                });

                                                                else sf.ui.izotope.windows.whoseTitle.is(moduleName).first.elementRaise();

                                                                1. In reply tochrscheuer:
                                                                  JJakub Trs @Jakub_Trs
                                                                    2021-11-29 13:14:03.291Z

                                                                    Hi Christian,
                                                                    this has me beyond excited - I've been reading the thread over and over to finally sort of understand how does the script work and how to implement it - and I finally succeeded a few minutes ago to control some of the sliders with my 1st gen Nano Kontrol !

                                                                    However, since a few moments the script doesn't work anymore and I'm at my wit's end - when pulling a fader up on the controller, this is the message from the log:

                                                                    29.11.2021 13:29:13.75 <info> [Backend]: Logging error in action (01) ClickButtonAction: Error invoking AXElement: kAXErrorCannotComplete
                                                                    !! Command Error: RX NanoKontrol Awesomeness [user:ckw2b4g8l0001x3109hi5yffm:ckw5bd4ww0000ip10spg0xll7]:
                                                                    Error invoking AXElement: kAXErrorCannotComplete (RX NanoKontrol Awesomeness: Line 55)
                                                                        SoundFlow.Shortcuts.AXUIElementException: kAXErrorCannotComplete
                                                                       at SoundFlow.Shortcuts.AXUIElement.DoAction(String) + 0x9a
                                                                       at SoundFlow.Shortcuts.Automation.Actions.ClickButtonAction.<Execute>d__11.MoveNext() + 0x89
                                                                    

                                                                    and when pulling it down, I get this:

                                                                    29.11.2021 14:09:42.79 <info> [Backend]: Logging error in action (01) ClickButtonAction: Error invoking AXElement: kAXErrorCannotComplete
                                                                    !! Command Error: RX NanoKontrol Awesomeness [user:ckw2b4g8l0001x3109hi5yffm:ckw5bd4ww0000ip10spg0xll7]:
                                                                    Error invoking AXElement: kAXErrorCannotComplete (RX NanoKontrol Awesomeness: Line 58)
                                                                        SoundFlow.Shortcuts.AXUIElementException: kAXErrorCannotComplete
                                                                       at SoundFlow.Shortcuts.AXUIElement.DoAction(String) + 0x9a
                                                                       at SoundFlow.Shortcuts.Automation.Actions.ClickButtonAction.<Execute>d__11.MoveNext() + 0x89
                                                                    

                                                                    The only thing I modified in the script are the MIDI CCs in the Config part.
                                                                    I'm on MacOS 11.1.6 with RX 9.1.1.

                                                                    I'm sure it's something silly, but "shallow" is the word I'd use to approximate the depth of my programming knowledge, so any insights would be appreciated.
                                                                    Thank you in advance!

                                                                    1. Hi Jakub,

                                                                      The kAXErrorCannotComplete error indicates that the target app (iZotope RX here) is busy doing other things so it can't bother replying to the Accessibility requests. If a restart of the app doesn't work, I'm not sure what would :/

                                                                      1. JJakub Trs @Jakub_Trs
                                                                          2021-11-29 17:30:44.950Z

                                                                          I see, I'll keep an eye on it. Thank you!

                                      • In reply topoterukha:
                                        Dustin Harris @Dustin_Harris
                                          2020-01-21 15:02:56.042Z

                                          This is fantastic! It is working for me, but I don't have the programming background to understand how or why.

                                          Question: If I wanted to take this existing code (specifically the slider input handling from post #20) and apply it only to specific sliders in specific windows, how would I do that?
                                          For instance, If I wanted CC14 to control

                                          sf.ui.izotope.windows.whoseTitle.is('Spectral Repair').first.groups.whoseDescription.is('EffectPanel Spectral Repair').first.sliders.whoseDescription.is('Surrounding region length [%]').first.elementRaise();
                                          

                                          and CC15 to control

                                          sf.ui.izotope.windows.whoseTitle.is('Spectral Repair').first.groups.whoseDescription.is('EffectPanel Spectral Repair').first.sliders.whoseDescription.is('Before/after weighting').first.elementRaise();
                                          

                                          Thanks!
                                          -D

                                          1. Hi Dustin,

                                            I think something like this should get you working:

                                            
                                            /* CONFIG HERE */
                                            
                                            const sliders = {
                                                14: () => sf.ui.izotope.windows.whoseTitle.is('Spectral Repair').first.groups.whoseDescription.is('EffectPanel Spectral Repair').first.sliders.whoseDescription.is('Surrounding region length [%]').first,
                                                15: () => sf.ui.izotope.windows.whoseTitle.is('Spectral Repair').first.groups.whoseDescription.is('EffectPanel Spectral Repair').first.sliders.whoseDescription.is('Before/after weighting').first,
                                            };
                                            
                                            /* SCRIPT, DON'T TOUCH */
                                            
                                            function setSliderValueDelta(slider, deltaValue) {
                                            
                                                if (!slider || !slider.exists) throw 'Could not find slider';
                                            
                                                var intDelta = Math.floor(deltaValue * 10);
                                                var absDelta = Math.abs(intDelta);
                                            
                                                if (intDelta > 0) {
                                                    for (var i = 0; i < absDelta; i++)
                                                        slider.elementClick({ actionName: 'AXIncrement' });
                                                } else if (intDelta < 0) {
                                                    for (var i = 0; i < absDelta; i++)
                                                        slider.elementClick({ actionName: 'AXDecrement' });
                                                }
                                            }
                                            
                                            function main() {
                                            
                                                var m = event.trigger.midiBytes;
                                                var m0 = m[0], m1 = m[1], m2 = m[2];
                                            
                                                var sliderFunc = sliders[m1];
                                            
                                                if (m0 == 176 && sliderFunc) {
                                                    //Get the UI item
                                                    var slider = sliderFunc();
                                            
                                                    //Get the last recorded slider value
                                                    var lastValue = globalState['lastSliderValue_' + m1];
                                            
                                                    if (m2 > lastValue) {
                                                        //The new value is larger than the last value, ie. positive delta
                                            
                                                        //Fader up
                                                        setSliderValueDelta(slider, 0.2);
                                                    } else if (m2 < lastValue) {
                                                        //The new value is smaller than the last value, ie. negative delta
                                            
                                                        //Fader down
                                                        setSliderValueDelta(slider, -0.2);
                                                    }
                                            
                                                    //Now store the current value as the last recorded slider value
                                                    globalState['lastSliderValue_' + m1] = m2;
                                                }
                                            
                                            }
                                            
                                            main();
                                            

                                            You set up the relationship between CC values and the sliders in the CONFIG HERE section.

                                            1. Dustin Harris @Dustin_Harris
                                                2020-01-22 04:11:56.750Z

                                                so in effect, I can use this script and assign controllers to any UX Slider element in the config section... this is unbelievably powerful. I'm so excited :)

                                                1. Yes exactly :) Bear in mind the MIDI trigger you create for this should be of the "ALL EVENTS" type so that the trigger doesn't prefilter on specific MIDI messages but trigger the script for all received MIDI events for that device. The script takes care of the filtering.

                                                  1. Dustin Harris @Dustin_Harris
                                                      2020-01-22 04:27:02.717Z

                                                      Would it be easy to adapt the script for things other than sliders? For instance, the Bands element in spectral repair is a dropdown menu instead of a slider, and I assume that would work by translating a CC positive delta to down arrow press + enter, and CC negative deltas to up arrow press + enter?

                                                      1. Sure. It would look a little different in the configuration, but here you go:

                                                        /* CONFIG HERE */
                                                        
                                                        const sliders = {
                                                            14: () => sliderDelta(sf.ui.izotope.windows.whoseTitle.is('Spectral Repair').first.groups.whoseDescription.is('EffectPanel Spectral Repair').first.sliders.whoseDescription.is('Surrounding region length [%]').first),
                                                            15: () => sliderDelta(sf.ui.izotope.windows.whoseTitle.is('Spectral Repair').first.groups.whoseDescription.is('EffectPanel Spectral Repair').first.sliders.whoseDescription.is('Before/after weighting').first),
                                                            16: () => dropDownDelta(sf.ui.izotope.windows.whoseTitle.is('Spectral Repair').first.groups.whoseDescription.is('EffectPanel Spectral Repair').first.popupButtons.whoseTitle.is('Bands').first),
                                                        };
                                                        
                                                        /* SCRIPT, DON'T TOUCH */
                                                        
                                                        function dropDownDelta(button) {
                                                            return deltaValue => {
                                                                button.mouseClickElement();
                                                                if (deltaValue < 0)
                                                                    sf.keyboard.press({ keys: 'up, enter' });
                                                                else
                                                                    sf.keyboard.press({ keys: 'down, enter' });
                                                            }
                                                        }
                                                        
                                                        function sliderDelta(slider) {
                                                            return deltaValue => {
                                                                if (!slider || !slider.exists) throw 'Could not find slider';
                                                        
                                                                var intDelta = deltaValue;
                                                                var absDelta = Math.abs(intDelta);
                                                        
                                                                if (intDelta > 0) {
                                                                    for (var i = 0; i < absDelta; i++)
                                                                        slider.elementClick({ actionName: 'AXIncrement' });
                                                                } else if (intDelta < 0) {
                                                                    for (var i = 0; i < absDelta; i++)
                                                                        slider.elementClick({ actionName: 'AXDecrement' });
                                                                }
                                                            }
                                                        }
                                                        
                                                        function main() {
                                                        
                                                            var m = event.trigger.midiBytes;
                                                            var m0 = m[0], m1 = m[1], m2 = m[2];
                                                        
                                                            var sliderFuncFunc = sliders[m1];
                                                        
                                                            if (m0 == 176 && sliderFuncFunc) {
                                                                //Get the UI item
                                                                var sliderDeltaFunc = sliderFuncFunc();
                                                        
                                                                //Get the last recorded slider value
                                                                var lastValue = globalState['lastSliderValue_' + m1];
                                                        
                                                                if (m2 > lastValue) {
                                                                    //The new value is larger than the last value, ie. positive delta
                                                        
                                                                    //Fader up
                                                                    sliderDeltaFunc(1);
                                                                } else if (m2 < lastValue) {
                                                                    //The new value is smaller than the last value, ie. negative delta
                                                        
                                                                    //Fader down
                                                                    sliderDeltaFunc(-1);
                                                                }
                                                        
                                                                //Now store the current value as the last recorded slider value
                                                                globalState['lastSliderValue_' + m1] = m2;
                                                            }
                                                        
                                                        }
                                                        
                                                        main();
                                                        
                                                        1. Dustin Harris @Dustin_Harris
                                                            2020-01-22 04:47:55.851Z

                                                            I’ll try this first thing in the morning. Thank you for such tireless work and support! Sound Flow is an incredible tool, but your support is really next level.
                                                            -D

                                                            1. Thank you Dustin :) Really appreciate it!

                                                              1. Dustin Harris @Dustin_Harris
                                                                  2021-07-23 14:11:30.863Z

                                                                  I recently came back to this script to find the mouse does not click where expected in this function in Big Sur:

                                                                  function dropDownDelta(button) {
                                                                      return deltaValue => {
                                                                          button.mouseClickElement();
                                                                          if (deltaValue < 0)
                                                                              sf.keyboard.press({ keys: 'up, enter' });
                                                                          else
                                                                              sf.keyboard.press({ keys: 'down, enter' });
                                                                      }
                                                                  }
                                                                  

                                                                  (it tends to want to scroll through the horizontal zoom instead)

                                                                  possibly related to this issue?

                                                                  1. Dustin Harris @Dustin_Harris
                                                                      2021-07-24 22:12:48.038Z

                                                                      Not the issue; solved with:

                                                                                  button.mouseClickElement({
                                                                                      relativePosition: { "x": 20, "y": 0 },
                                                                                      anchor: "MidCenter",
                                                                                  });
                                                                      
                                                    • In reply topoterukha:
                                                      Marek Bert Fuchs @bert
                                                        2023-11-28 00:20:05.413Z

                                                        Hello @chrscheuer,

                                                        can you please do a quick code modification for me? I tried to edit that, but with no success. I have an Asparion D400 controller and with this script it increments or decrements only by one step every time.

                                                        I think this is because different behavior of the controller, than the common uses.

                                                        D400 sends this:

                                                        Knob with right rotation: CC 75: 65 = Increment
                                                        Knob with left rotation: CC 75: 1 = Decrement

                                                        Thank you in advance. It would help me a lot!

                                                        M. Bert F.

                                                        1. Try changing this:

                                                          if (m2 > lastValue) {
                                                                      //The new value is larger than the last value, ie. positive delta
                                                          
                                                                      //Fader up
                                                                      sliderDeltaFunc(1);
                                                                  } else if (m2 < lastValue) {
                                                                      //The new value is smaller than the last value, ie. negative delta
                                                          
                                                                      //Fader down
                                                                      sliderDeltaFunc(-1);
                                                                  }
                                                          

                                                          to:

                                                          if (m2 > 64) {
                                                                      //The new value is an increment value
                                                          
                                                                      //Fader up
                                                                      sliderDeltaFunc(1);
                                                                  } else if (m2 < 64) {
                                                                      //The new value is a decrement value
                                                          
                                                                      //Fader down
                                                                      sliderDeltaFunc(-1);
                                                                  }
                                                          
                                                          1. Marek Bert Fuchs @bert
                                                              2023-11-28 15:02:33.126Z2023-11-28 15:08:42.540Z

                                                              Thanks! It works like a charm!

                                                              For anyone looking to set specific slider sensitivity after the control touch:

                                                              var intDelta = Math.floor(deltaValue * # enter your multiply number here #);
                                                              

                                                              Example (multiply sens by 10x):

                                                              var intDelta = Math.floor(deltaValue * 10);
                                                              

                                                              You can implement this to template by:

                                                              var intDelta = Math.floor(deltaValue * Number(event.props.gain));
                                                              

                                                              M. Bert F
                                                              Eklen Sounds