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

Logic Pro X Bouncing selected tracks as Stems

By Alex Oldroyd @Alex_Oldroyd8
    2022-08-05 15:37:28.098Z

    Title

    Logic Pro X Bouncing selected tracks as Stems

    What do you expect to happen when you run the script/macro?

    Script should read which tracks are selected and apply script to each. 1. ensure track is solo enabled 2. open bounce track dialogue and select all options as desired 3. open desired bounce folder 4. name bounce file name of track with pre or post script as desired 5. bounce and wait for bounce to complete 6. move to next selected track and repeat

    Are you seeing an error?

    The errors are numerous and seemingly random. Often re-running the script completely solves any issues.

    Ideally i'd like to sort out these errors in the first place but additionally - is there a way to use error handling like loop until success when calling a function? ie loop bouncestem() until there is no error? I can only find information on doing that in relation to selecting a popup menu item for example.

    Here are some of the most common errors below
    05.08.2022 16:25:52.03 [Backend]: !! Command Error: !! BOUNCE ALL STEMS [user:cl1dfytrs0001lz10tvlm49uj:cl1dg31150004lz103udco1eh]:
    Error: Object reference not set to an instance of an object.
    (!! BOUNCE ALL STEMS line 84)

    05.08.2022 16:27:10.63 [Backend]: !! Command Error: !! BOUNCE ALL STEMS [user:cl1dfytrs0001lz10tvlm49uj:cl1dg31150004lz103udco1eh]:
    Couldn't locate AxElementArrayIndexedItem (!! BOUNCE ALL STEMS: Line 31)

    05.08.2022 16:28:07.10 [Backend]: !! Command Error: !! BOUNCE ALL STEMS [user:cl1dfytrs0001lz10tvlm49uj:cl1dg31150004lz103udco1eh]:
    Object reference not set to an instance of an object. (!! BOUNCE ALL STEMS: Line 141)
    System.NullReferenceException: Object reference not set to an instance of an object.
    at SoundFlow.Shortcuts.Ax.AxNodes.AxElementArrayIndexedItem.GetUIElement() + 0x3e
    at SoundFlow.Shortcuts.Ax.AxNodes.AxElement.LoadUIElement() + 0x1b
    at SoundFlow.Shortcuts.Ax.AxNodes.AxElement.get_UIElement() + 0x36
    at SoundFlow.Shortcuts.Automation.AutoActionResult.RequireUI(AxElement, String) + 0x24
    at SoundFlow.Shortcuts.Automation.Actions.ClickButtonAction.d__11.MoveNext() + 0x40
    --- End of stack trace from previous location where exception was thrown ---
    at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() + 0x1c
    at sfbackend!+0x14f0935
    at sfbackend!+0x14f0866
    at SoundFlow.Shortcuts.Automation.AutoAction`1.d__20.MoveNext() + 0x2e2

    What happens when you run this script?

    The script will often work on one or two tracks, then an error will occur and cause the script to fail. I then have to work out how far it has got and re-start from the failed point. Of course an automatic bouncing script like this is only really useful if it will reliably run from start to finish... otherwise i have to monitor it which ruins the entire point!!

    Thanks a lot

    How were you running this script?

    I used a Stream Deck button

    How important is this issue to you?

    5

    Details

    {
        "inputExpected": "Script should read which tracks are selected and apply script to each. \n1. ensure track is solo enabled\n2. open bounce track dialogue and select all options as desired\n3. open desired bounce folder\n4. name bounce file name of track with pre or post script as desired\n5. bounce and wait for bounce to complete\n6. move to next selected track and repeat",
        "inputIsError": true,
        "inputError": "The errors are numerous and seemingly random. Often re-running the script completely solves any issues.\n\nIdeally i'd like to sort out these errors in the first place but additionally - is there a way to use error handling like loop until success when calling a function? ie loop bouncestem() until there is no error? I can only find information on doing that in relation to selecting a popup menu item for example.\n\nHere are some of the most common errors below\n05.08.2022 16:25:52.03  [Backend]: !! Command Error: !! BOUNCE ALL STEMS [user:cl1dfytrs0001lz10tvlm49uj:cl1dg31150004lz103udco1eh]:\nError: Object reference not set to an instance of an object.\n(!! BOUNCE ALL STEMS line 84) \n\n05.08.2022 16:27:10.63  [Backend]: !! Command Error: !! BOUNCE ALL STEMS [user:cl1dfytrs0001lz10tvlm49uj:cl1dg31150004lz103udco1eh]:\nCouldn't locate AxElementArrayIndexedItem (!! BOUNCE ALL STEMS: Line 31)\n\n05.08.2022 16:28:07.10  [Backend]: !! Command Error: !! BOUNCE ALL STEMS [user:cl1dfytrs0001lz10tvlm49uj:cl1dg31150004lz103udco1eh]:\nObject reference not set to an instance of an object. (!! BOUNCE ALL STEMS: Line 141)\n    System.NullReferenceException: Object reference not set to an instance of an object.\n   at SoundFlow.Shortcuts.Ax.AxNodes.AxElementArrayIndexedItem.GetUIElement() + 0x3e\n   at SoundFlow.Shortcuts.Ax.AxNodes.AxElement.LoadUIElement() + 0x1b\n   at SoundFlow.Shortcuts.Ax.AxNodes.AxElement.get_UIElement() + 0x36\n   at SoundFlow.Shortcuts.Automation.AutoActionResult.RequireUI(AxElement, String) + 0x24\n   at SoundFlow.Shortcuts.Automation.Actions.ClickButtonAction.d__11.MoveNext() + 0x40\n--- End of stack trace from previous location where exception was thrown ---\n   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() + 0x1c\n   at sfbackend!+0x14f0935\n   at sfbackend!+0x14f0866\n   at SoundFlow.Shortcuts.Automation.AutoAction`1.d__20.MoveNext() + 0x2e2",
        "inputWhatHappens": "The script will often work on one or two tracks, then an error will occur and cause the script to fail. I then have to work out how far it has got and re-start from the failed point. Of course an automatic bouncing script like this is only really useful if it will reliably run from start to finish... otherwise i have to monitor it which ruins the entire point!!\n\nThanks a lot",
        "inputHowRun": {
            "key": "-MpfwmPg-2Sb-HxHQAff",
            "title": "I used a Stream Deck button"
        },
        "inputImportance": 5,
        "inputTitle": "Logic Pro X Bouncing selected tracks as Stems"
    }

    Source

    //ask where you would like stems to be bounced to
    const bounceFolder = prompt("Where would you like to bounce");
    
    //preText or postText?
    const preText = prompt("PreText?")
    const postText = prompt("postText?")
    
    bounceStems()
    
    function bounceStems() {
        const app = sf.ui.app('com.apple.logic10')
        const logic = sf.ui.app("com.apple.logic10");
        logic.appActivateMainWindow()
    
    
        /**
         * @param {object} obj
         * @param {'Solo'|'Mute'} obj.buttonName
         * @param {'Enable'|'Disable'|'Toggle'} [obj.targetValue]
         */
        //make sure solo button is engaged
        function ensureButton({ buttonName, targetValue }) {
    
            const logic = sf.ui.app('com.apple.logic10');
            const inspector = logic.mainWindow.groups.whoseDescription.is("Inspector").first
            const mixer = inspector.children.whoseRole.is("AXList").first.groups.allItems[returnCorrectAllitemsNumber()].children.whoseRole.is("AXLayoutArea").whoseDescription.is("Mixer").first
            const soloButtonMixer = mixer.children.whoseRole.is("AXLayoutItem").first.buttons.whoseDescription.is(buttonName).first;
    
            function isTrackSoloButtonEnabled() {
                //const soloButtonMixer = sf.ui.app("com.apple.logic10").mainWindow.groups.whoseDescription.is("Inspector").first.children.whoseRole.is("AXList").first.groups.allItems[returnCorrectAllitemsNumber()].children.whoseRole.is("AXLayoutArea").whoseDescription.is("Mixer").first.children.whoseRole.is("AXLayoutItem").first.buttons.whoseDescription.is("solo").first;
                return soloButtonMixer.value.invalidate().value
            }
    
            const isButtonEngaged = isTrackSoloButtonEnabled() === 'on';
    
            switch (true) {
                case targetValue === "Enable" && !isButtonEngaged:
                    soloButtonMixer.mouseClickElement({
                        anchor: "MidCenter",
                        isOption: true,
                    });
                    break;
                case targetValue === "Disable" && isButtonEngaged:
                    soloButtonMixer.mouseClickElement({
                        anchor: "MidCenter",
                        isOption: true,
                    });
                    break;
                case targetValue === "Toggle":
                    soloButtonMixer.mouseClickElement({
                        anchor: "MidCenter",
                        isOption: true,
                    });
                    break;
            }
        }
    
        //attempt to check name. If there's an error, it's probably still bouncing so repeat.
        function waitForBounceToComplete() {
            while (true) {
                try {
                    if (checkTrackName()) {
                        //We didn't get an error - and the modal popup doesn't exist.
                        //Break out, the bounce is complete
                        sf.keyboard.press({ keys: 'escape' })
                        break;
                    }
                } catch (err) {
                    //We got an error. Logic is still busy. Continue with another loop
                }
    
                //Wait a bit before continuing
                sf.wait({ intervalMs: 200 });
            }
    
        }
    
        function bounceStem() {
            // Get Selected Track and Project Name
            const logic = sf.ui.app('com.apple.logic10');
            const trackArea = logic.mainWindow.groups.whoseDescription.is('Tracks').first.groups.whoseDescription.is('Tracks');
            const trackHeaders = trackArea.allItems[1].splitGroups.first.splitGroups.allItems[1].scrollAreas.first.groups.whoseDescription.is('Tracks header').first;
            const selectedTrackName = trackHeaders.getElements('AXSelectedChildren').first.getString("AXDescription").match(/“(.*)”/)[1]
            const projectName = logic.mainWindow.getElement("AXTitleUIElement").value.invalidate().value.split(" - ")[0];
            const bounceButton = sf.ui.app("com.apple.logic10").windows.first.buttons.whoseTitle.is("Bounce").first
            const stemName = `${preText}${selectedTrackName}${postText}`
            sf.keyboard.press({ keys: 'escape' })
            ensureButton({
                buttonName: "Solo",
                targetValue: "Enable",
            });
            app.getMenuItem('File', 'Bounce', 'Project or Section…').elementClick();
            app.children.whoseRole.is("AXWindow").first.scrollAreas.first.tables.first.children.whoseRole.is("AXRow").first.children.whoseRole.is("AXCell").first.checkBoxes.first.checkboxSet({
                targetValue: "Enable",
                onError: "Continue",
            });
            app.children.whoseRole.is("AXWindow").first.scrollAreas.first.tables.first.children.whoseRole.is("AXRow").first.children.whoseRole.is("AXCell").allItems[1].getElement("AXTitleUIElement").mouseClickElement({
                anchor: "MidCenter",
                onError: "Continue",
            });
            app.children.whoseRole.is("AXWindow").first.scrollAreas.first.tables.first.children.whoseRole.is("AXRow").allItems[1].children.whoseRole.is("AXCell").first.checkBoxes.first.checkboxSet({
                targetValue: "Disable",
                onError: "Continue",
            });
            app.children.whoseRole.is("AXWindow").first.scrollAreas.first.tables.first.children.whoseRole.is("AXRow").allItems[2].children.whoseRole.is("AXCell").first.checkBoxes.first.checkboxSet({
                targetValue: "Disable",
                onError: "Continue",
            });
            app.children.whoseRole.is("AXWindow").first.scrollAreas.first.tables.first.children.whoseRole.is("AXRow").allItems[3].children.whoseRole.is("AXCell").first.checkBoxes.first.checkboxSet({
                targetValue: "Disable",
                onError: "Continue",
            });
            app.children.whoseRole.is("AXWindow").first.groups.first.checkBoxes.whoseTitle.is("Add to Project").first.checkboxSet({
                targetValue: "Disable",
                onError: "Continue",
            });
            app.children.whoseRole.is("AXWindow").first.groups.first.popupButtons.allItems[1].popupMenuSelect({
                menuPath: ["Wave"],
                onError: "Continue",
            });
            sf.ui.app("com.apple.logic10").windows.first.buttons.whoseTitle.is("OK").first.elementClick();
    
    
            //Wait for the progress window to appear
            sf.wait({ intervalMs: 2000 });
    
            searchForBounceFolder(bounceFolder);
    
            // Set the "Save as..." Field
            logic.windows.first.textFields.first.elementSetTextAreaValue({
                value: stemName
            });
    
            sf.wait({ intervalMs: 1000 });
    
            if (bounceButton.exists) {
                bounceButton.elementClick();
            }
            else {
                sf.wait({ intervalMs: 2000 });
                bounceButton.elementClick();
            }
    
            //Wait for the progress window to disappear
            waitForBounceToComplete();
            sf.wait({ intervalMs: 2000 });
            sf.keyboard.press({ keys: 'escape' })
    
            ensureButton({
                buttonName: "Solo",
                targetValue: "Disable",
            });
        }
    
        function checkTrackName() {
            sf.keyboard.press({ keys: 'escape' })
            sf.clipboard.clear();
            app.getMenuItem('Track', 'Rename Track').elementClick();
            app.getMenuItem('Edit', 'Copy').elementClick();
            sf.keyboard.press({ keys: 'escape' })
            var trackName = sf.clipboard.waitForText().text;
            sf.wait({ intervalMs: 100 })
            return trackName
        }
    
        function doForEachSelectedTrack(functionToDo) {
            const logic = sf.ui.app('com.apple.logic10');
            const trackArea = logic.mainWindow.groups.whoseDescription.is('Tracks').first.groups.whoseDescription.is('Tracks');
            const trackHeaders = trackArea.allItems[1].splitGroups.first.splitGroups.allItems[1].scrollAreas.first.groups.whoseDescription.is('Tracks header').first;
            //Get Selected track names
            const selectedTrackNames = trackHeaders.getElements('AXSelectedChildren').map(trackHeader => trackHeader.getString("AXDescription").match(/“(.*)”/)[1]);
            selectedTrackNames.forEach(item => functionToDo(item));
        }
    
        function selectTrackAndPerformFunction(trackName) {
            let originalMousePosition = sf.mouse.getPosition().position;
    
            const logicApp = sf.ui.app("com.apple.logic10");
    
            const trackToSelect = () => {
                const trackHeaders = logicApp.invalidate().mainWindow.groups.whoseDescription.is("Tracks").first
                    .groups.whoseDescription.is("Tracks").allItems[1].splitGroups.first.splitGroups.allItems[1]
                    .scrollAreas.first.groups.whoseDescription.is("Tracks header").first.children;
    
                const trackToSelect = trackHeaders.find(x => x.textFields.whoseDescription.is(trackName).first.exists);
                return trackToSelect
            }
    
            if (!trackToSelect()) {
                log(`Track with the name '${trackName}' not found`)
                throw 0;
            }
    
            const mainWindowFrame = logicApp.mainWindow.frame;
            const logicRulerFrame = logicApp.mainWindow.groups.whoseDescription.is("Tracks").first.groups.whoseDescription.is("Tracks").allItems[1]
                .splitGroups.first.splitGroups.first.scrollAreas.first.frame;
    
            const trackFrame = trackToSelect().frame;
            const mainWindowTotalY = (mainWindowFrame.y + mainWindowFrame.h);
            const offWindowBottom = ((trackFrame.y + trackFrame.h) > (mainWindowFrame.y + mainWindowFrame.h));
            const offScreenTop = (trackFrame.y < logicRulerFrame.y + logicRulerFrame.h);
    
            const trackAreaFrame = sf.ui.app("com.apple.logic10").mainWindow.groups.whoseDescription.is("Tracks").first
                .groups.whoseDescription.is("Tracks").allItems[1].splitGroups.first.splitGroups.allItems[1].scrollAreas.allItems[1].frame;
    
            const middleOfTrackHeaderPane = { x: trackAreaFrame.x + (trackAreaFrame.w / 2), y: trackAreaFrame.y + (trackAreaFrame.h / 2) };
    
            sf.mouse.setPosition({ position: middleOfTrackHeaderPane })
    
            if (offWindowBottom) {
                sf.mouse.setPosition({ position: middleOfTrackHeaderPane })
                sf.mouse.scroll({
                    delta: -(trackFrame.y + trackFrame.h - mainWindowTotalY),
                    delta2: 0,
                    unit: "Pixel"
                })
            }
    
            else if (offScreenTop) {
                sf.mouse.setPosition({ position: middleOfTrackHeaderPane })
                sf.mouse.scroll({
                    delta: ((logicRulerFrame.y + logicRulerFrame.h) - trackFrame.y),
                    delta2: 0,
                    unit: "Pixel"
                })
            }
    
            trackToSelect().mouseClickElement({
                relativePosition: { x: (trackFrame.w / 2), y: 5 }
            })
    
            sf.mouse.setPosition({ position: originalMousePosition })
    
            /*
                    try {
                        bounceStem()
                    }
                    catch (error) {
                        sf.keyboard.press({
                            keys: "escape",
                        });
                        log(trackName);
                        bounceStem();
                    }
                    */
    
            bounceStem();
    
    
        }
    
        doForEachSelectedTrack(selectTrackAndPerformFunction);
    }
    
    function whatTypeOfTrack() {
        const logic = sf.ui.app("com.apple.logic10");
        const channel = logic.mainWindow.groups.whoseDescription.is("Inspector").first.children.whoseRole.is("AXList").first.groups.allItems[3].children.whoseRole.is("AXLayoutArea").whoseDescription.is("Mixer").first.children.whoseRole.is("AXLayoutItem")
        const inputMonitoring = logic.mainWindow.groups.whoseDescription.is("Inspector").first.children.whoseRole.is("AXList").first.groups.allItems[2].children.whoseRole.is("AXLayoutArea").whoseDescription.is("Mixer").first.children.whoseRole.is("AXLayoutItem").first.buttons.whoseDescription.is("monitoring").first;
        const inputMonitoringOther = logic.mainWindow.groups.whoseDescription.is("Inspector").first.children.whoseRole.is("AXList").first.groups.allItems[3].children.whoseRole.is("AXLayoutArea").whoseDescription.is("Mixer").first.children.whoseRole.is("AXLayoutItem").first.buttons.whoseDescription.is("monitoring").first;
        const monoStereo = logic.mainWindow.groups.whoseDescription.is("Inspector").first.children.whoseRole.is("AXList").first.groups.allItems[2].children.whoseRole.is("AXLayoutArea").whoseDescription.is("Mixer").first.children.whoseRole.is("AXLayoutItem").first.buttons.whoseDescription.is("channel mode").first;
        const monoStereoOther = logic.mainWindow.groups.whoseDescription.is("Inspector").first.children.whoseRole.is("AXList").first.groups.allItems[3].children.whoseRole.is("AXLayoutArea").whoseDescription.is("Mixer").first.children.whoseRole.is("AXLayoutItem").first.buttons.whoseDescription.is("channel mode").first;
    
        //if audio
        if ((inputMonitoring.exists) || (inputMonitoringOther.exists)) {
            return ('Audio')
        }
    
        //if aux
        if (((monoStereo.exists) || (monoStereoOther.exists) && (!inputMonitoring.exists))) {
            return ('Aux')
        }
    
        //if instrument
        if (!monoStereo.exists) {
            return ('Instrument')
        }
    
    }
    
    function returnCorrectAllitemsNumber() {
        if (whatTypeOfTrack() === 'Aux') {
            return '2'
        }
        if (whatTypeOfTrack() === 'Audio') {
            return '3'
        }
        if (whatTypeOfTrack() === 'Instrument') {
            return '3'
        }
    }
    
    
    function searchForBounceFolder(folderPath) {
    
        const logic = sf.ui.app("com.apple.logic10")
        const bounceDlg = logic.windows.first;
        // Shortcut for opening the "Go To Folder" sheet. (Keyboard simulation is needed here)
        sf.keyboard.press({ keys: "cmd+shift+g", });
    
        // Wait for sheet
        bounceDlg.sheets.first.elementWaitFor();
    
        // Set Folder Path New
        bounceDlg.sheets.first.comboBoxes.first.elementSetTextAreaValue({
            value: folderPath,
            onError: "Continue",
        });
    
        // Close "Go To Folder" sheet
        sf.keyboard.press({ keys: "return" });
        bounceDlg.sheets.first.elementWaitFor({ waitForNoElement: true });
    
    }
    

    Links

    User UID: Kf7km0JU15Q6NnRTk6HrX1PmHaw1

    Feedback Key: sffeedback:Kf7km0JU15Q6NnRTk6HrX1PmHaw1:-N8ids8mHzF4HGThHhHF

    Feedback ZIP

    Solved in post #2, click to view
    • 2 replies
    1. Kitch Membery @Kitch2022-08-08 18:34:31.080Z

      Hi @Alex_Oldroyd8,

      It looks like Christian was able to answer your error handling question in the following thread;

      Are any of these errors happening on 100% of each run of the script?

      As you mentioned, ideally sorting out these errors in the first place is the best approach. If you are able to narrow down and reproduce these random errors with smaller scripts, it may be worthwhile logging bug reports for them via the help/issue workflow. That way it will be easier for us to investigate further.

      I will say that I've noticed I occasionally get a "Object reference not set to an instance of an object." error in Logic, but have neglected to log a report for it. I'll be sure to do so if I can narrow things down when I see the error next.

      Rock on!

      ReplySolution
      1. AAlex Oldroyd @Alex_Oldroyd8
          2022-08-09 01:21:05.222Z

          Great thanks Kitch. I will log the errors next time they come up