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.
- Christian Scheuer @chrscheuer2019-12-27 15:01:01.841Z
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 usingif
blocks to then determine what to do:
https://forum.soundflow.org/-1407/creating-macros-for-audiomidi-clip-movement-in-nuendocubase-with-midi-knobs - In reply topoterukha⬆:Vladimir @poterukha
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 executessetSliderValueDelta(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) ; }
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; }
Vladimir @poterukha
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 bysetSliderValueDelta
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/sZjhVrXBSQwzkwChristian Scheuer @chrscheuer2020-01-02 21:55:54.568Z
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.... ;)Christian Scheuer @chrscheuer2020-01-02 22:00:03.644Z
Note I just edited the script above to become a little simpler.
- In reply tochrscheuer⬆:
Vladimir @poterukha
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.Christian Scheuer @chrscheuer2020-01-02 22:11:29.191Z
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.Vladimir @poterukha
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)
Christian Scheuer @chrscheuer2020-01-02 22:27:41.843Z
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?Vladimir @poterukha
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.
- In reply tochrscheuer⬆:
Vladimir @poterukha
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
or2nd 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 calledNoise gain [dB]
or2nd Slider
and it isn't controlling with any slider number atsetSliderValueDelta(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.Christian Scheuer @chrscheuer2020-01-03 17:30:06.400Z
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 atsetSliderValueDelta
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 );
Vladimir @poterukha
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.Christian Scheuer @chrscheuer2020-01-03 18:27:53.291Z
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.Vladimir @poterukha
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.Christian Scheuer @chrscheuer2020-01-04 19:36:18.452Z
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.Christian Scheuer @chrscheuer2020-01-04 20:05:38.287Z
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?
Christian Scheuer @chrscheuer2020-01-04 20:08:07.570Z
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();
Christian Scheuer @chrscheuer2020-01-04 20:09:44.294Z
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.
- In reply tochrscheuer⬆:
Vladimir @poterukha
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();
- In reply tochrscheuer⬆:JJakub Trs @Jakub_Trs
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!Christian Scheuer @chrscheuer2021-11-29 17:19:51.133Z
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 :/- JJakub Trs @Jakub_Trs
I see, I'll keep an eye on it. Thank you!
- In reply topoterukha⬆:Dustin Harris @Dustin_Harris
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 controlsf.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!
-DChristian Scheuer @chrscheuer2020-01-22 04:05:42.603Z
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.
Dustin Harris @Dustin_Harris
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 :)
Christian Scheuer @chrscheuer2020-01-22 04:18:10.179Z
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.
Dustin Harris @Dustin_Harris
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?
Christian Scheuer @chrscheuer2020-01-22 04:37:45.910Z
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();
Dustin Harris @Dustin_Harris
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.
-DChristian Scheuer @chrscheuer2020-01-22 05:00:10.128Z
Thank you Dustin :) Really appreciate it!
Dustin Harris @Dustin_Harris
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?
Dustin Harris @Dustin_Harris
Not the issue; solved with:
button.mouseClickElement({ relativePosition: { "x": 20, "y": 0 }, anchor: "MidCenter", });
- In reply topoterukha⬆:Marek Bert Fuchs @bert
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 = DecrementThank you in advance. It would help me a lot!
M. Bert F.
Christian Scheuer @chrscheuer2023-11-28 10:43:32.987Z
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); }
Marek Bert Fuchs @bert
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