No internet connection
  1. Home
  2. Macro and Script Help

RX 9/10 Restore Selection button access

By darcytaranto @darcytaranto
    2022-09-06 06:18:59.838Z

    Hi there,

    I've managed to cobble together a working script in Soundflow over the last week from bits of code I found in other threads relating to RX.
    The script is designed to automate a sequence of manual operations I perform nearly everyday on a series audio files, to prepare them for further manual work.

    The sequence consists of running two module chain presets on each file, in series (not parallel), one after the other. (The reason for series is I use several instances of the External Plugin module which I have found will often get confused and load the wrong external plugin if another instance is running on a different file at the same time.)

    However, after running the first Module Chain preset on each file, with the entire file selected I use RX 9's Restore Selection feature to bring forward the 'Initial State' of the entire file into its Undo History. Essentially this 'resets' the file to that initial state whilst keeping the all the history of the first Module Chain's operations. Next I run the second Module Chain preset which is like a 'B' to the first preset's 'A' and this creates a kind of A/B setup within RX's Undo History panel, looking like this:

    Or folded down like this:

    So then I can make a selection as I work, and restore that selection to the first or 'A' Module Chain's output to utilise the different version of processing. I'm not sure if iZotope figured the feature would be used like this but it works perfectly for me.

    Currently, my script does everything I need except the Restore Selection action, as I'm not sure how to access this button:

    I currently run 1 version of the script, do the Restore myself manually for each file, and then run a 2nd version to do the 2nd preset.
    Here's the script (I'm sure lots of it will be familiar to anyone who's seen other RX forum posts):

    sf.app.launch({
        path: "/Applications/iZotope RX 9 Audio Editor.app",
    });
    
    sf.ui.izotope.appWaitForActive();
    
    
    function ensureModuleChainIsOpen() {
        var win = sf.ui.izotope.windows.whoseTitle.is('Module Chain').first;
        if (!win.exists) {
            sf.ui.izotope.menuClick({
                menuPath: ['Window', 'Module Chain']
            });
            win.elementWaitFor();
        }
    }
    
    function selectModuleChainPreset(name) {
        var btn = sf.ui.izotope.windows.whoseTitle.is('Module Chain').first.groups.whoseDescription.is('Module Chain Panel').first.popupButtons.whoseDescription.is('Preset').first;
        if (btn.value.invalidate().value == name) return;
        btn.elementClick();
        sf.keyboard.press({ keys: 'enter' });
        while (true) {
            sf.engine.checkForCancellation();
            if (btn.value.invalidate().value == name) break;
            sf.keyboard.press({ keys: 'down' });
        }
    }
    
    function processOpenModuleChain(numentries) {
        let mainWindow = 'RX' + sf.ui.izotope.activeBundleID.slice(-1) + " Main Window";
        let undoHistoryItems = sf.ui.izotope.mainWindow.groups.whoseDescription.is(mainWindow).first.groups.whoseDescription.is("Undo Panel").invalidate().first.radioButtons
        let currentUndoHistoryIndex = undoHistoryItems.map((button, index) => {
            if (button.isCheckBoxChecked) return index
        }).filter(button => button)[0]
        while (true) {
            if (currentUndoHistoryIndex >= numentries) {
                
                /* if (numentries === 19){
                    code to bring forward
    
                } */
                throw 0;
        } else {
            sf.ui.izotope.windows.whoseTitle.is('Module Chain').first.groups.whoseDescription.is('Module Chain Panel').first.buttons.whoseDescription.is('Render').first.elementClick();
            break;
             }
        }
    }
    
    function nextFileWhenRenderWindowHasClosed(numentries) {
        while (true) {
            if (sf.ui.izotope.windows.whoseTitle.endsWith('wav').first.groups.whoseDescription.is("Task Progress View").first.exists) {
                log("processing")
            } else {
                sf.ui.izotope.menuClick({
                    menuPath: ['Window', 'Next File']
                });
                processOpenModuleChain(numentries);
            }
            sf.wait({ intervalMs: 5000 })
        }
    }
    
    
    /**
    * @param {string} name
    * @param {number} numentries
    */
    function processModuleChain(name, numentries) {
        sf.ui.izotope.invalidate();
        sf.ui.izotope.appActivateMainWindow();
        ensureModuleChainIsOpen();
        selectModuleChainPreset(name);
        processOpenModuleChain(numentries);
        nextFileWhenRenderWindowHasClosed(numentries);
    }
    
    processModuleChain('BGI-16,-3.1,New', 19);
    //processModuleChain('BGI-16,-3.1,Legacy', 21);
    

    So basically, I need to insert a bit that does the Restore Selection action where I've got the commented out bit in here:

     while (true) {
            if (currentUndoHistoryIndex >= numentries) {
                
                /* if (numentries === 19){
                    code to bring forward
    
                } */
                throw 0;
        } else {
            sf.ui.izotope.windows.whoseTitle.is('Module Chain').first.groups.whoseDescription.is('Module Chain Panel').first.buttons.whoseDescription.is('Render').first.elementClick();
            break;
             }
    

    Then I'll need to configure it to switch to the 2nd preset.

    I've been incredibly impressed with the support offered here from Soundflow crew, providing people tailored scripts for people and their needs. I wanted to see how much I could do myself before seeking help, and I'm really glad to have spent the time putting my script together so far so in order to learn how SF works and be able to create my own macros in the future hopefully.

    Hopefully this thread can help other people needing similar actions in RX, especially the Restore Selection button as I don't think it's been asked about here before?

    Thanks in advance to anyone who can offer some help!
    Darcy

    • 4 replies
    1. D
      darcytaranto @darcytaranto
        2022-09-19 03:59:02.231Z

        Just realised this was in the wrong category, so I've changed that now.

        1. Dustin Harris @Dustin_Harris
            2022-09-19 12:59:08.640Z

            Heyya Darcy!

            This is what I've been using; it's a bit of a hack since it uses mouse clicks (the restore button element itself doesn't seem to be visible to SF).

            /**
            * @param {RectangleF} elementFrame
            */
            function clickRestoreButton(elementFrame) {
                sf.mouse.setPosition({
                    position: { x: elementFrame.x + elementFrame.w - 21, y: elementFrame.y + (elementFrame.h / 2) }
                })
            
                sf.mouse.click({
                    position: { x: elementFrame.x + elementFrame.w - 21, y: elementFrame.y + (elementFrame.h / 2) }
                })
            }
            

            Once you add the above function to your script, you can use it like this:

            /* get the element of the previous step in the undo history via it's index in the array */
            const restoreSourceElement = undoHistoryItems[currentUndoHistoryIndex - 1]
            
            /* pass the element frame into the clickRestoreButton function */
            clickRestoreButton(restoreSourceElement.frame)
            

            Hopefully this helps, and let me know how it goes... it's a bit of a hack so it could be unpredictable. :)

            1. Ddarcytaranto @darcytaranto
                2022-09-20 12:03:19.349Z

                Thanks so much for this Dustin, I'll test it out and report back with my findings

                1. Ddarcytaranto @darcytaranto
                    2022-09-26 04:25:08.125Z

                    Yo, okay so firstly I'd like to say that your button clicker totally works for me, so that's amazing thank you so much.

                    And it took a few days of working through my script to get it working how I want, but I'm proud to preset my current working version in full.
                    It's not elegant at all, but it does what I want so I'm just going to leave it for a while :-)

                    sf.app.launch({
                        path: "/Applications/iZotope RX 9 Audio Editor.app",
                    });
                    
                    sf.ui.izotope.appWaitForActive();
                    
                    
                    function ensureModuleChainIsOpen() {
                        log("Module Chain Open?");
                        var win = sf.ui.izotope.windows.whoseTitle.is('Module Chain').first;
                        if (!win.exists) {
                            sf.ui.izotope.menuClick({
                                menuPath: ['Window', 'Module Chain']
                            });
                            win.elementWaitFor();
                        }
                    }
                    
                    function selectModuleChainPreset(name) {
                        var btn = sf.ui.izotope.windows.whoseTitle.is('Module Chain').first.groups.whoseDescription.is('Module Chain Panel').first.popupButtons.whoseDescription.is('Preset').first;
                        if (btn.value.invalidate().value == name) {
                            log("Correct preset");
                            return;
                        }
                        btn.elementClick();
                        sf.keyboard.press({ keys: 'enter' });
                    
                        let count = 0;
                        
                        while (true) {
                            if (count == 0) {
                               log("Switching preset");
                            }
                            sf.engine.checkForCancellation();
                            if (btn.value.invalidate().value == name) break;
                            sf.keyboard.press({ keys: 'down' });
                            count++;
                        }
                    }
                    
                    function processOpenModuleChain(numentries) {
                        let mainWindow = 'RX' + sf.ui.izotope.activeBundleID.slice(-1) + " Main Window";
                        let undoHistoryItems = sf.ui.izotope.mainWindow.groups.whoseDescription.is(mainWindow).first.groups.whoseDescription.is("Undo Panel").invalidate().first.radioButtons;
                        let currentUndoHistoryIndex = undoHistoryItems.map((button, index) => {
                            if (button.isCheckBoxChecked) return index
                        }).filter(button => button)[0];
                        log(currentUndoHistoryIndex);
                        while (true) {
                            if (currentUndoHistoryIndex == 39) {
                                log("done");
                                throw 0;
                            }
                            if (currentUndoHistoryIndex == numentries) {
                                sf.wait({ intervalMs: 1000});
                                /* get the element of the previous step in the undo history via it's index in the array */
                                const restoreSourceElement = undoHistoryItems[currentUndoHistoryIndex - 19]
                                sf.wait({ intervalMs: 1000});
                                /* pass the element frame into the clickRestoreButton function */
                                clickRestoreButton(restoreSourceElement.frame)
                                log("Restored Initial State");
                                sf.wait({ intervalMs: 1000});
                                processModuleChain('BGI-16,-3.1,Legacy', 21);
                                
                            } else {
                                sf.ui.izotope.windows.whoseTitle.is('Module Chain').first.groups.whoseDescription.is('Module Chain Panel').first.buttons.whoseDescription.is('Render').first.elementClick();
                                break;
                            }
                        }
                    }
                    
                    function nextFileWhenRenderWindowHasClosed(numentries) {
                        while (true) {
                            let mainWindow = 'RX' + sf.ui.izotope.activeBundleID.slice(-1) + " Main Window";
                            let undoHistoryItems = sf.ui.izotope.mainWindow.groups.whoseDescription.is(mainWindow).first.groups.whoseDescription.is("Undo Panel").invalidate().first.radioButtons;
                            let currentUndoHistoryIndex = undoHistoryItems.map((button, index) => {
                            if (button.isCheckBoxChecked) return index
                            }).filter(button => button)[0]
                            log(currentUndoHistoryIndex);
                            if (sf.ui.izotope.windows.whoseTitle.endsWith('wav').first.groups.whoseDescription.is("Task Progress View").first.exists) {
                                log("processing");
                            } else {
                                if (currentUndoHistoryIndex == 39) {
                                    sf.ui.izotope.menuClick({
                                    menuPath: ['Window', 'Next File']
                                    });
                                    log("next file");
                                    processModuleChain('BGI-16,-3.1,New', 19);
                                    break;
                                }
                                else {
                                    log("2nd half");
                                    processOpenModuleChain(numentries);
                                }
                            }
                            sf.wait({ intervalMs: 5000 })
                    
                        }
                    }
                    
                    
                    
                    
                    /**
                    * @param {RectangleF} elementFrame
                    */
                    function clickRestoreButton(elementFrame) {
                        sf.mouse.setPosition({
                            position: { x: elementFrame.x + elementFrame.w - 21, y: elementFrame.y + (elementFrame.h / 2) }
                        })
                    
                        sf.mouse.click({
                            position: { x: elementFrame.x + elementFrame.w - 21, y: elementFrame.y + (elementFrame.h / 2) }
                        })
                    }
                    
                    /**
                    * @param {string} name
                    * @param {number} numentries
                    */
                    function processModuleChain(name, numentries) {
                        sf.ui.izotope.invalidate();
                        sf.ui.izotope.appActivateMainWindow();
                        ensureModuleChainIsOpen();
                        selectModuleChainPreset(name);
                        processOpenModuleChain(numentries);
                        nextFileWhenRenderWindowHasClosed(numentries);
                    }
                    
                    log("From the top");
                    processModuleChain('BGI-16,-3.1,New', 19);
                    log("should be done!");
                    //processModuleChain('BGI-16,-3.1,Legacy', 21);
                    //processModuleChain('EQ');
                    
                    
                    

                    It's sort of all over the place but the logic works so I'm happy :-)
                    (I had to add loads of log entries to work out where it was and where it was failing as I put it together)