No internet connection
  1. Home
  2. Packages
  3. Stem Bounce

Bounce Tracks script failing with PT2023.3+ and latest Soundflow

By T Michaels @T_Michaels
    2023-11-08 20:36:55.310Z

    Title

    Bounce Tracks script failing with PT2023.3+ and latest Soundflow

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

    main use of this script is to bounce stems based on naming in the comments section of each track

    Are you seeing an error?

    Could not ensure main window became frontmost after waiting 2000 ms (Bounce Tracks: Line 1)

    Comes up as soon as I hit bounce stems. I just updated Soundflow and now this happens, previously the script would run but would preset an error rearranging the windows at the end, but the stems would bounce, originally with an older version of Pro Tools it ran properly

    What happens when you run this script?

    No matter how I start the script I get the error message as soon as I try to start it

    Could not ensure main window became frontmost after waiting 2000 ms (Bounce Tracks: Line 1)

    How were you running this script?

    I clicked the "Run Script" or "Run Macro" button in SoundFlow

    How important is this issue to you?

    5

    Details

    {
        "inputExpected": "main use of this script is to bounce stems based on naming in the comments section of each track",
        "inputIsError": true,
        "inputError": "Could not ensure main window became frontmost after waiting 2000 ms (Bounce Tracks: Line 1)\n\nComes up as soon as I hit bounce stems. I just updated Soundflow and now this happens, previously the script would run but would preset an error rearranging the windows at the end, but the stems would bounce, originally with an older version of Pro Tools it ran properly",
        "inputWhatHappens": "No matter how I start the script I get the error message as soon as I try to start it\n\nCould not ensure main window became frontmost after waiting 2000 ms (Bounce Tracks: Line 1)\n",
        "inputHowRun": {
            "key": "-MpfwYA4I6GGlXgvp5j1",
            "title": "I clicked the \"Run Script\" or \"Run Macro\" button in SoundFlow"
        },
        "inputImportance": 5,
        "inputTitle": "Bounce Tracks script failing with PT2023.3+ and latest Soundflow"
    }

    Source

    sf.ui.proTools.appActivateMainWindow()
    sf.ui.proTools.windows.invalidate()
    sf.ui.proTools.mainWindow.invalidate()
    
    let bouncedStems = []
    
    
    // FUNCTIONS -------------------------------------------------------------------------------------
    
    // SAVE USER CONFIG WINDOW
    function saveUserWinConfig() {
        sf.ui.proTools.appActivateMainWindow()
        sf.ui.proTools.menuClick({ menuPath: ['Window', 'Configurations', 'New Configuration...'] })
        // Wait for the Mem Location dialog to appear and assign it to the memLocDlg variable
        const winConfigDlg = sf.ui.proTools.dialogWaitForManual({
            dialogTitle: 'New Window Configuration'
        }).dialog
    
        // We want to save a window layout
        winConfigDlg.radioButtons[1].elementClick()
    
        // We want to include all windows
        winConfigDlg.checkBoxes[0].checkboxSet({ targetValue: 'Enable' })
    
        // Name the Location Track Visibilty
        winConfigDlg.textFields.allItems[1].elementSetTextFieldWithAreaValue({ value: 'User Window Setup' })
    
        // Get the Location Number and pass it back so we can delete it when we're done
        const configNumber = winConfigDlg.textFields.allItems[0].value.invalidate().value
    
        // Create the Location
        winConfigDlg.buttons.whoseTitle.is('OK').first.elementClick()
    
        sf.ui.proTools.windows.whoseTitle.is('New Window Configuration').first.elementWaitFor({
            waitType: 'Disappear'
        })
    
        return configNumber
    }
    
    // RECALL AND DELETE WINDOW CONFIG
    function recallAndDeleteWinConfig(configNumber) {
        sf.ui.proTools.appActivateMainWindow()
        sf.ui.proTools.menuClick({
            menuPath: ['Window', 'Configurations', 'Window Configuration List'],
            targetValue: 'Enable'
        })
        sf.ui.proTools.windows.whoseTitle.is('Window Configurations').first.elementWaitFor()
    
        const winConfigWindow = sf.ui.proTools.windows.whoseTitle.is('Window Configurations').first
    
        // Recall Window Config which will also leave it selected for deleting
        const configNumStr = configNumber.toString()
    
        let winConfigWords = 'numpad comma, '
        for (let i = 0; i < configNumStr.length; i++) {
            winConfigWords = winConfigWords.concat('numpad ' + configNumStr[i] + ', ')
        }
    
        let keys = winConfigWords + 'numpad multiply'
        sf.keyboard.press({ keys: keys })
    
        sf.ui.proTools.windows.whoseTitle.is('Window Configurations').first.buttons.first.popupMenuSelect({
            menuSelector: items => items.filter(i => i.names[0].match(/^Clear \"/))[0]
        })
    
        sf.ui.proTools.menuClick({
            menuPath: ['Window', 'Configurations', 'Window Configuration List'],
            targetValue: 'Disable'  //or 'Enable' or 'Toggle'
        })
    
        sf.ui.proTools.windows.whoseTitle.is('Window Configurations').first.elementWaitFor({
            waitType: 'Disappear'
        })
    }
    
    // SET SELECTED TRACKS SIZE
    function setSelectedTracksSize(size) {
        const f = sf.ui.proTools.selectedTrack.frame
        const popupMenu = sf.ui.proTools.selectedTrack.popupMenuOpenFromElement({
            relativePosition: { x: f.w - 10, y: 5 },
            isOption: true,
            isShift: true
        }).popupMenu
    
        popupMenu.menuClickPopupMenu({ menuPath: [size] })
    }
    
    // SAVE CURRENT TRACK VIEW
    function saveCurrentTrackView() {
        sf.ui.proTools.appActivateMainWindow()
        sf.keyboard.press({ keys: 'numpad enter' })
        sf.ui.proTools.newMemoryLocationDialog.elementWaitFor()
    
        //Wait for the Mem Location dialog to appear and assign it to the memLocDlg variable
        const memLocDlg = sf.ui.proTools.dialogWaitForManual({
            dialogTitle: 'New Memory Location'
        }).dialog
    
        // We only want to store the track visibilty so clear all check boxes and then check the appropriate one
        let checkBoxes = memLocDlg.getElements('AXChildren').filter(function (e) { return e.fullRole == 'AXCheckBox' })
        for (let i = 0; i < 6; i++) {
            checkBoxes[i].checkboxSet({
                targetValue: 'Disable'
            })
        }
        
        checkBoxes[2].checkboxSet({ targetValue: 'Enable' })
        checkBoxes[3].checkboxSet({ targetValue: 'Enable' })
        
        //Name the Location Track Visibilty
        sf.ui.proTools.newMemoryLocationDialog.textFields.allItems[1].elementSetTextFieldWithAreaValue({
            value: 'Track Visibility'
        })
        //Get the Location Number and pass it back so we can delete it when we're done
        const locationNumber = sf.ui.proTools.newMemoryLocationDialog.textFields.allItems[2].value.value
    
        // Set time Properties to none
        sf.ui.proTools.newMemoryLocationDialog.radioButtons.allItems[2].elementClick()
    
        //Create the Location
        sf.ui.proTools.newMemoryLocationDialog.buttons.whoseTitle.is('OK').first.elementClick()
    
        sf.ui.proTools.newMemoryLocationDialog.elementWaitFor({ waitType: 'Disappear' })
    
        return locationNumber
    }
    
    // RECALL AND DELETE TRACK VIEW
    function recallAndDeleteTrackView(locNumber) {
    
        sf.ui.proTools.memoryLocationsGoto({
            memoryLocationNumber: Number(locNumber)
        })
    
        sf.ui.proTools.memoryLocationsShowWindow()
    
        sf.ui.proTools.memoryLocationsWindow.popupButtons.whoseTitle.is('Memory Locations').first.popupMenuSelect({
            menuSelector: items => items.filter(i => i.names[0].match(/^Clear \"/))[0]
        })
    
        sf.ui.proTools.menuClick({
            menuPath: ['Window', 'Memory Locations'],
            targetValue: 'Disable'  //or 'Enable' or 'Toggle'
        })
        
        sf.ui.proTools.memoryLocationsWindow.elementWaitFor({
            waitType: 'Disappear'
        })
    
    }
    
    // SUSPEND GROUPS
    function suspendGroups(status) {
        sf.ui.proTools.groupsEnsureGroupListIsVisible()
    
        const groupListPopup = sf.ui.proTools.groupsOpenListPopupMenu().popupMenu
        groupListPopup.menuClickPopupMenu({
            menuPath: ['Suspend All Groups'],
            targetValue: status
        })
    
        sf.ui.proTools.appActivateMainWindow()
    }
    
    // LINK TRACK AND EDIT SELECTION
    function linkTrackAndEditSelection() {
        if (!sf.ui.proTools.getMenuItem('Options', 'Link Track and Edit Selection').isMenuChecked) {
            sf.ui.proTools.menuClick({
                menuPath: ["Options", "Link Track and Edit Selection"],
            })
        }
    }
    
    // SETUP SCREEN
    function setupScreen() {
        // Make sure we're on the Edit window
        if (!sf.ui.proTools.getMenuItem('Window', 'Edit').isMenuChecked) {
            sf.ui.proTools.menuClick({ menuPath: ['Window', 'Edit'] })
        }
    
        // Make sure comments are visible
        if (!sf.ui.proTools.getMenuItem('View', 'Edit Window Views', 'Comments').isMenuChecked) {
            sf.ui.proTools.menuClick({ menuPath: ['View', 'Edit Window Views', 'Comments'] })
        }
    }
    
    // GET SESSION SAMPLE-RATE AND BIT-DEPTH
    function getSessionFormat() {
        sf.ui.proTools.menuClick({ menuPath: ['Setup', 'Session'] })
    
        let sessionDlg = sf.ui.proTools.dialogWaitForManual({
            dialogTitle: 'Session Setup'
        }).dialog
    
        sf.ui.proTools.windows.whoseTitle.is('Session Setup').waitFor()
    
        let sampleRate = sessionDlg.groups.whoseTitle.is('Session Format').first.children.whoseRole.is('AXStaticText').allItems[2].value.invalidate().value
        let bitDepth = sessionDlg.groups.whoseTitle.is('Session Format').first.children.whoseRole.is('AXPopUpButton').allItems[1].value.invalidate().value
    
        sf.ui.proTools.viewCloseFocusedFloatingWindow()
    
        return [sampleRate, bitDepth]
    }
    
    // INPUT STEM PREFIX
    function inputStemPrefix() {
        return sf.interaction.popupText({
            popupIdentifier: 'stemPrefix',
            title: 'Stem Prefix'
        }).text
    }
    
    // SELECT BOUNCE OUTPUT
    function selectBounceOutput() {
        sf.ui.proTools.menuClick({
            menuPath: ['File', 'Bounce Mix...'],
        })
    
        let bounceDlg = sf.ui.proTools.dialogWaitForManual({
            dialogTitle: 'Bounce Mix'
        }).dialog
    
        sf.ui.proTools.windows.whoseTitle.is('Bounce Mix').waitFor()
    
        let mainPanelBtns = bounceDlg.getElements('AXChildren').filter(function (e) { return e.fullRole == 'AXPopUpButton' })
        let outputBtn = mainPanelBtns[1]
        let outputs = outputBtn.popupMenuFetchAllItems().menuItems
        let outputGroups = []
        let groupOutputs = []
    
        for (let i = 0; i < outputs.length; i++) {
            const outputGroup = outputs[i].path[0]
    
            if (!outputGroups.includes(outputGroup)) {
                outputGroups.push(outputGroup)
            }
        }
    
        let outputGroupsList = []
        outputGroups.map(outputGroup => outputGroupsList.push({ name: outputGroup }) )
    
        sf.ui.proTools.appActivateMainWindow()
        sf.ui.proTools.windows.whoseTitle.is('Bounce Mix').first.buttons.whoseTitle.is('Cancel').first.elementClick()
    
        sf.ui.proTools.waitForNoModals()
    
        while (sf.ui.frontmostApp.title.value !== 'Pro Tools') {
            sf.wait({ intervalMs: 500 })
        }
    
        const selectedOutputGroup = sf.interaction.popupSearch({
            items: outputGroupsList,
            title: 'Select output group'
        }).item.name
    
        for (let i = 0; i < outputs.length; i++) {
            const outputGroup = outputs[i].path[0]
    
            if (outputGroup === selectedOutputGroup) {
                groupOutputs.push(outputs[i].path[1])
            }
        }
    
        let groupOutputsList = []
        groupOutputs.map(groupOutput => groupOutputsList.push({ name: groupOutput }) )
    
        const selectedOutput = sf.interaction.popupSearch({
            items: groupOutputsList,
            title: 'Select output'
        }).item.name
    
        return [selectedOutputGroup, selectedOutput]
    }
    
    // GET STEM NAMES
    function getStemNames(selectedTracks) {
        let stems = []
    
        for (let i = 0; i < selectedTracks.length; i++) {
            const stemName = sf.ui.proTools.trackGetByName({ name: selectedTracks[i] }).track.textFields.first.value.value
    
            if (!stems.includes(stemName)) {
                stems.push(stemName)
            }
        }
    
        return stems
    }
    
    // CHANGE TRACK ACTIVITY
    function changeTrackActivity(status) {
        if (sf.ui.proTools.getMenuItem('Track', status).exists) {
            sf.ui.proTools.menuClick({ menuPath: ['Track', status] })
        }
        
        sf.wait()
    }
    
    // SELECT AND SCROLL TO TRACK
    function selectAndScrollTracks(tracks) {
        sf.ui.proTools.menuClick({ menuPath: ['Track', 'Scroll to Track...'] })
        sf.ui.proTools.confirmationDialog.elementWaitFor()
        sf.ui.proTools.confirmationDialog.textFields.first.elementSetTextFieldWithAreaValue({ value: tracks[0] })
        sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is('OK').first.elementClick()
        sf.ui.proTools.confirmationDialog.elementWaitFor({ waitType: 'Disappear' })
        sf.ui.proTools.trackSelectByName({ names: tracks, deselectOthers: true })
    }
    
    // GET TRACK WIDTH
    function getTrackWidth(track) {
        var width = track.groups.whoseTitle.is('Audio IO').first.sliders.whoseTitle.contains('Pan').count
        return width
    }
    
    // SET DYN VALUE
    function setDynPopupBtn(targetValue) {
        const btn = sf.ui.proTools.selectedTrack.popupButtons.whoseTitle.startsWith('Voice selector').first
    
        btn.popupMenuSelect({
            menuPath: [targetValue],
            isOption: true,
            isShift: true
        })
    }
    
    // SET TRACK VOICE SELECTOR
    function setTrackVoiceSelector({ targetValue }) {
        sf.ui.proTools.appActivateMainWindow()
        sf.ui.proTools.mainWindow.invalidate()
    
        const originalTrackNames = sf.ui.proTools.selectedTrackNames
    
        const selectedTrackHeaders = sf.ui.proTools.selectedTrackHeaders
    
        const trackInfo = selectedTrackHeaders.map(track => ({
            trackTitle: track.normalizedTrackName,
            trackType: track.title.invalidate().value.split(' - ')[1].trim(),
            trackWidth: getTrackWidth(track)
        }))
    
        setSelectedTracksSize('medium')
    
        // @ts-ignore
        const trackWidths = [...new Set(trackInfo.map(t => t.trackWidth))]
    
        trackWidths.forEach(trackWidth => {
    
            if (trackWidth > 0 && trackWidth <= 2) {
                let trackNames = trackInfo
                    .filter(track => track.trackWidth === trackWidth)
                    .map(track => track.trackTitle)
    
                let selectedTrackName = sf.ui.proTools.selectedTrack.normalizedTrackName
    
                selectAndScrollTracks(trackNames)
    
                setDynPopupBtn(targetValue)
            }
        })
    
        selectAndScrollTracks(originalTrackNames)
    
        setSelectedTracksSize('mini')
    
        selectAndScrollTracks(originalTrackNames)
    }
    
    // BOUNCE STEMS
    function bounceStems(selectedTracks, stems, stemPrefix, sessionFormat, bounceOutput) {
        
        let firstLoop = true
    
        // Loop through stems
        stems.forEach(stem => {
            // Deselect all tracks
            sf.ui.proTools.trackDeselectAll()
    
            // Select tracks with stem name in comments
            selectedTracks.forEach(track => {
                const trackComment = sf.ui.proTools.trackGetByName({ name: track }).track.textFields.first.value.value
    
                if (trackComment === stem) {
                    sf.ui.proTools.trackSelectByName({ names: [track], deselectOthers: false })
                }
            })
    
            // Scroll selected tracks into view
            selectAndScrollTracks(sf.ui.proTools.selectedTracks.names)
    
            // Click first selected track (solves selection bug in PT)
            const trackFrame = sf.ui.proTools.selectedTrack.frame
            sf.mouse.click({ position: { 'x': trackFrame.x + 45, 'y': trackFrame.y + 10 } })
    
            // Make current stem tracks active, solo or dyn off
            switch (event.props.method) {
                case 'Activity':
                    changeTrackActivity('Make Active')
                    break
                case 'Solo':
                    sf.keyboard.press({ keys: 'shift+s' })
                    break
                case 'Dyn':
                    setTrackVoiceSelector({ targetValue: 'dyn' })
            }
    
            if (event.props.exportType === 'Masters') {
                // Set selection length if none
                if (sf.ui.proTools.selectionGetInfo().isEmpty) {
                    sf.ui.proTools.mainWindow.groups.whoseTitle.is("Counter Display Cluster").first.textFields.whoseTitle.is("Edit Selection Length").first.elementClick()
                    sf.keyboard.type({ text: '1' })
                    sf.keyboard.press({ keys: 'return' })
                }
    
                // Select all clips in track
                sf.ui.proTools.children.whoseRole.is('AXMenuBar').whoseTitle.is('').first.children.whoseRole.is('AXMenuBarItem').whoseTitle.is('Edit').first.popupMenuSelect({
                    menuPath: ['Select All']
                })
            }
    
            // BOUNCE STEM TO DISK
            try {
                bounceStem(stem, firstLoop, stemPrefix, sessionFormat, bounceOutput)
            } catch(err) {
                // Make current stem tracks inactive
                switch (event.props.method) {
                case 'Activity':
                    changeTrackActivity('Make Inactive')
                    break
                case 'Solo':
                    sf.keyboard.press({ keys: 'shift+s' })
                    break
                case 'Dyn':
                    setTrackVoiceSelector({ targetValue: 'off' })
            }
                throw err
            }
    
            firstLoop = false
    
            // Make current stem tracks inactive
            switch (event.props.method) {
                case 'Activity':
                    changeTrackActivity('Make Inactive')
                    break
                case 'Solo':
                    sf.keyboard.press({ keys: 'shift+s' })
                    break
                case 'Dyn':
                    setTrackVoiceSelector({ targetValue: 'off' })
            }
        })
    
        return bouncedStems
    }
    
    // BOUNCE STEM
    function bounceStem(stemName, firstRun, stemPrefix, sessionFormat, output) {
    
        // Open Bounce dialogue
        sf.ui.proTools.menuClick({ menuPath: ['File', 'Bounce Mix...'] })
        let bounceDlg = sf.ui.proTools.dialogWaitForManual({
            dialogTitle: 'Bounce Mix'
        }).dialog
    
        //File Name
        switch (event.props.exportType) {
            case 'Stems':
                bounceDlg.textFields.whoseTitle.is('').first.elementSetTextFieldWithAreaValue({
                    value: stemPrefix + '_' + event.props.stemSuffix + '_' + stemName
                })
                break
            case 'Masters':
                bounceDlg.textFields.whoseTitle.is('').first.elementSetTextFieldWithAreaValue({
                    value: stemName
                })
                break
        }
    
        if (firstRun === true) {
            const audioPanel = bounceDlg.groups.whoseTitle.is('Audio').first
            const locationPanel = bounceDlg.groups.whoseTitle.is('Location').first
    
            // Maximise bounce window
            function maximiseBounceWindow() {
                function openAudioPanel() {
                    if (audioPanel.buttons.whoseTitle.is('Collapser').exists) {
                        audioPanel.buttons.whoseTitle.is('Collapser').first.elementClick()
                    }
                }
                function openLocationPanel() {
                    if (locationPanel.buttons.whoseTitle.is('Collapser').exists) {
                        locationPanel.buttons.whoseTitle.is('Collapser').first.elementClick()
                    }
                }
                switch (bounceDlg.frame.h) {
                    case 268:
                        openAudioPanel()
                        openLocationPanel()
                        break
                    case 406:
                        openLocationPanel()
                        break
                    case 393:
                        openAudioPanel()
                        break
                }
            }
    
            maximiseBounceWindow()
    
            let mainPanelBtns = bounceDlg.getElements('AXChildren').filter(function (e) { return e.fullRole == 'AXPopUpButton' })
            let audioPanelBtns = audioPanel.getElements('AXChildren').filter(function (e) { return e.fullRole == 'AXPopUpButton' })
            let mainCheckboxes = bounceDlg.getElements('AXChildren').filter(function (e) { return e.fullRole == 'AXCheckBox' })
            let audioCheckboxes = audioPanel.getElements('AXChildren').filter(function (e) { return e.fullRole == 'AXCheckBox' })
            let locationCheckboxes = locationPanel.getElements('AXChildren').filter(function (e) { return e.fullRole == 'AXCheckBox' })
            let locationRadios = locationPanel.getElements('AXChildren').filter(function (e) { return e.fullRole == 'AXRadioButton' })
            let locationText = locationPanel.getElements('AXChildren').filter(function (e) { return e.fullRole == 'AXTextField' })
    
            let fileTypeBtn = mainPanelBtns[0]
            let outputBtn = mainPanelBtns[1]
            let formatBtn = audioPanelBtns[1]
            let bitDepthBtn = audioPanelBtns[2]
            let sampleRateBtn = audioPanelBtns[3]
            let mp3CheckBox = audioCheckboxes[0]
            let padCheckBox = audioCheckboxes[1]
            let importCheckBox = locationCheckboxes[0]
            let sessionFolderButton = locationRadios[0]
            let sessionFolderText = locationText[0]
            let offlineCheckBox = mainCheckboxes[0]
    
            let sampleRate = sessionFormat[0]
            let bitDepth = sessionFormat[1]
    
            //Set Output
            if (outputBtn.value.invalidate().value != output) {
                outputBtn.popupMenuSelect({
                    menuPath: output,
                    useWildcards: true
                })
            }
    
            //File Type
            if (fileTypeBtn.value.invalidate().value != event.props.fileType)
                fileTypeBtn.popupMenuSelect({ menuPath: [event.props.fileType] })
    
            //File Format
            if (formatBtn.value.value != 'Interleaved')
                formatBtn.popupMenuSelect({ menuPath: ['Interleaved'] })
    
            if (event.props.fileType !== 'MP3') {
                //Bit Depth
                if (event.props.bitDepth === 'session') {
                    if (bitDepthBtn.value.invalidate().value != bitDepth)
                        bitDepthBtn.popupMenuSelect({ menuPath: [bitDepth] })
                } else {
                    if (bitDepthBtn.value.invalidate().value != event.props.bitDepth)
                        bitDepthBtn.popupMenuSelect({ menuPath: [event.props.bitDepth] })
                }
    
                // Add MP3
                mp3CheckBox.checkboxSet({
                    targetValue: event.props.addMP3 ? 'Enable' : 'Disable',
                })
    
                //Import After Bounce
                importCheckBox.checkboxSet({
                    targetValue: 'Disable',
                })
    
                // Pad To Frame Boundary
                padCheckBox.checkboxSet({
                    targetValue: 'Disable',
                })
            }
    
            //Sample Rate
            if (event.props.sampleRate === 'session') {
                if (sampleRateBtn.value.invalidate().value != sampleRate)
                    sampleRateBtn.popupMenuSelect({ menuPath: [sampleRate] })
            } else {
                if (sampleRateBtn.value.invalidate().value != event.props.sampleRate)
                    sampleRateBtn.popupMenuSelect({ menuPath: [event.props.sampleRate] })
            }
    
            // Session Folder
            sessionFolderButton.elementClick()
    
            // Session Folder Name
            switch (event.props.exportType) {
                case 'Stems':
                    sessionFolderText.elementSetTextFieldWithAreaValue({
                        value: 'Bounced Files/' + stemPrefix + '_' + event.props.folderSuffix
                    })
                    break
                case 'Masters':
                    sessionFolderText.elementSetTextFieldWithAreaValue({
                        value: 'Bounced Files/'
                    })
                    break
            }
    
            // Offline Bounce
            offlineCheckBox.checkboxSet({
                targetValue: event.props.offline
            })
            
        }
    
        sf.wait({ intervalMs: 200 })
    
        sf.ui.proTools.windows.whoseTitle.is('Bounce Mix').first.buttons.whoseTitle.is('Bounce').first.elementClick()
    
        let replaceDlg = sf.ui.proTools.windows.whoseTitle.is("").first.children.whoseRole.is("AXStaticText").whoseValue.matches(/\" already exists\.  Do you want to replace it\?/).first
    
        if (replaceDlg.exists) {
            let yesBtn =  sf.ui.proTools.windows.whoseTitle.is("").first.children.whoseRole.is("AXButton").whoseTitle.is("Yes").first
            let noBtn =  sf.ui.proTools.windows.whoseTitle.is("").first.children.whoseRole.is("AXButton").whoseTitle.is("No").first
    
            switch (event.props.overwriteExistingFiles) {
                case 'Yes':
                    yesBtn.elementClick()
                    sf.ui.proTools.waitForNoModals()
                    bouncedStems.push(stemName)
                    break
                case 'No':
                    noBtn.elementClick()
                    sf.ui.proTools.windows.whoseTitle.is('Bounce Mix').first.buttons.whoseTitle.is('Cancel').first.elementClick()
                    sf.ui.proTools.waitForNoModals()
                    throw 'Stem "' + stemName + '" already exists in ' + stemPrefix + '_' + event.props.folderSuffix
                case 'Dismiss':
                    noBtn.elementClick()
                    sf.ui.proTools.windows.whoseTitle.is('Bounce Mix').first.buttons.whoseTitle.is('Cancel').first.elementClick()
                    sf.ui.proTools.waitForNoModals()
                    return
            }
        } else {
            bouncedStems.push(stemName)
        }
    
        // MP3 dialogue
        if (event.props.fileType === 'MP3' || event.props.addMP3) {
            let mp3Dlg = sf.ui.proTools.dialogWaitForManual({
                dialogTitle: 'MP3'
            }).dialog
    
            if (firstRun === true) {
                let mainMp3Btns = mp3Dlg.getElements('AXChildren').filter(function (e) { return e.fullRole == 'AXPopUpButton' })
    
                let encodingSpeedBtn = mainMp3Btns.length === 4 ? mainMp3Btns[3] : mainMp3Btns[2]
                let cbrBtn = mainMp3Btns.length === 4 ? mainMp3Btns[2] : mainMp3Btns[1]
                let tagBtn = mainMp3Btns.length === 4 ? mainMp3Btns[1] : mainMp3Btns[0]
    
                if (tagBtn.value.invalidate().value != 'None')
                    tagBtn.popupMenuSelect({ menuPath: ['None'] })
    
                if (cbrBtn.value.invalidate().value != '320kbit/s')
                    cbrBtn.popupMenuSelect({ menuPath: ['320kbit/s (' + sessionFormat[0].replace(' k', '000') + ')'] })
    
                if (encodingSpeedBtn.value.invalidate().value != 'Slowest')
                    encodingSpeedBtn.popupMenuSelect({ menuPath: ['Highest Quality, Slower Encoding Time'] })
            }
    
            sf.ui.proTools.windows.whoseTitle.is('MP3').first.buttons.whoseTitle.is('OK').first.elementClick()
        }
    
        sf.ui.proTools.waitForNoModals()
    
        while (sf.ui.frontmostApp.title.value !== 'Pro Tools') {
            sf.wait({ intervalMs: 500 })
        }
        
    }
    
    // MAIN ------------------------------------------------------------------------------------------
    function main() {
        // Get selected tracks
        const selectedTracks = sf.ui.proTools.trackGetSelectedTracks().names
        const stems = getStemNames(selectedTracks)
    
        if (sf.ui.proTools.selectedTracks.trackHeaders.filter(e => e.title.value.includes('Folder')).length > 0 && event.props.method ==='Dyn') {
            sf.interaction.displayDialog({
                title: 'Stem Bouncing Stopped',
                prompt: 'Method cannot be set to Dyn On/Off if using folder tracks in your selection.'
            })
    
            return
        }
    
        selectAndScrollTracks(selectedTracks)
    
        const stemPrefix = event.props.exportType === 'Stems' ? inputStemPrefix() : ''
        sf.ui.proTools.appActivateMainWindow()
        const bounceOutput = selectBounceOutput()
    
        // Save the way the user has the screen set up and setup screen
        const winConfigNum = saveUserWinConfig()
        const tracksViewNum = saveCurrentTrackView()
        sf.ui.proTools.viewCloseFloatingWindows()
        setupScreen()
        setSelectedTracksSize('mini')
        suspendGroups('Enable')
        linkTrackAndEditSelection()
        switch (event.props.method) {
            case 'Activity':
                changeTrackActivity('Make Inactive')
                break
            case 'Solo':
                sf.ui.proTools.mainWindow.counterDisplay.mouseClickElement({
                    relativePosition: { x: 299, y: 67 }
                })
                break
            case 'Dyn':
                setTrackVoiceSelector({ targetValue: 'off' })
        }
    
        // Get session format
        const sessionFormat = getSessionFormat()
    
        // Bounce stems
        try {
            bounceStems(selectedTracks, stems, stemPrefix, sessionFormat, bounceOutput)
        } catch(err) {
            // Error dialogue
            sf.interaction.displayDialog({
                title: 'Stems Bouncing Stopped',
                prompt: err
            })
        }
    
        // Finish and cleanup
        selectAndScrollTracks(selectedTracks)
        switch (event.props.method) {
            case 'Activity':
                changeTrackActivity('Make Active')
                break
            case 'Solo':
                sf.ui.proTools.mainWindow.counterDisplay.mouseClickElement({
                    relativePosition: { x: 299, y: 67 }
                })
                break
            case 'Dyn':
                setTrackVoiceSelector({ targetValue: 'dyn' })
        }
        suspendGroups('Disable')
        recallAndDeleteTrackView(tracksViewNum)
        recallAndDeleteWinConfig(winConfigNum)
    
        // Complete dialogue
        sf.interaction.displayDialog({
            title: 'Stems Complete',
            prompt: 'Bounced ' + bouncedStems.length + ' ' + event.props.exportType.toLowerCase() + ' of ' + stems.length + '.'
        })
    
    }
    
    main()
    

    Links

    User UID: DfctjZy0W0bRsTARiwy1IVULtWK2

    Feedback Key: sffeedback:DfctjZy0W0bRsTARiwy1IVULtWK2:-NikdjdlvS1HuExs95NE

    Feedback ZIP: pFw7FEVbYG9A/xJk43qacnwia8+Eabr4h7wma7ahYHmPWxdZ7AhTZX9iguebJxUYtN/5CXMzUjmPiA5yAwqJ3ncUKiBdMmrH+0WQz2bfhVVsXgS3LHdqIKqMf+zv5lNdOebumTRMtQXek9af9K6FTf6U7+cZphfiyHU0wOLYYgkSn7npsa7tquti6URW352nbyzW8Lj5DgQbmRRxFEnvaJ6L2qyUihWmPilTXDjxqmOL15UQ3hI4OS7DZK7kObyUy435WjQguHLkEQqtBUr/pniccE7/a/oNMot4rrFZxBvAFcjqDC4exQm2KADBDiObnHez36W2QsQbtPjb4u5Bdg==

    • 11 replies
    1. S
      SoundFlow Bot @soundflowbot
        2023-11-08 20:36:58.370Z

        Thanks for posting a question or an issue related to the 'Stem Bounce' package.
        This package is made by @David_Simpson. We're auto-tagging them here so that they will hopefully be able to help you.

        1. C
          In reply toT_Michaels:
          chras bretta @chras_bretta
            2024-02-08 17:47:08.496Z

            In PT 23.12.0 and latest SF. Mine gets hung up on line 281:
            08.02.2024 12:46:48.77 [Backend]: Logging unknown error in action (02) RunCommandAction: Bounce Tracks: Line 281

            08.02.2024 12:46:48.77 [Backend]: !! Command Error: MASTERS Print CB [user:clg152hf20000us107phzuil9:cknizud3m0004ns10vps6gb90#clmpfxabw0000tp10o2og7l9q]:
            Couldn't locate AxElementArrayIndexedItem (Bounce Tracks: Line 281)

            << Command: MASTERS Print CB [user:clg152hf20000us107phzuil9:cknizud3m0004ns10vps6gb90#clmpfxabw0000tp10o2og7l9q]

            08.02.2024 12:46:48.77 [StartMenu]: StartMenuContainer currentPageKey='home' Line 24630 file:///Applications/SoundFlow.app/Contents/Helpers/SoundFlow.app/Contents/Resources/app.asar//dist/bundle.js
            08.02.2024 12:46:48.95 [StartMenu]: StartMenuContainer currentPageKey='home' Line 24630 file:///Applications/SoundFlow.app/Contents/Helpers/SoundFlow.app/Contents/Resources/app.asar//dist/bundle.js
            08.02.2024 12:46:51.98 [EditorWindow:Renderer]: Active Focus Container: app Line 33963 file:///Applications/SoundFlow.app/Contents/Helpers/SoundFlow.app/Contents/Resources/app.asar/dist/editor.js

            If @David_Simpson is out there, I'll hire you to update this script!!

            1. D
              In reply toT_Michaels:
              David Simpson @David_Simpson
                2024-04-14 10:34:04.897Z

                Hi all,

                Apologies for the delay in getting to this. I've now updated the script to work with Pro Tools 2024.3 so let me know if you're still having issues and I'll try my best to get to them.

                Cheers!
                David

                1. T
                  In reply toT_Michaels:
                  T Michaels @T_Michaels
                    2024-04-15 11:15:54.939Z

                    Thank you so much David! Really appreciate this.

                    I'm running 2023.9 due to being on Big Sur, stems now bounce although it fails on putting the track size etc back, I don't mind as I always do a save as session for a stem bounce.

                    Running perfectly on 2024.3, I'm just reluctant to use that since it's not officially supported on Big Sur!

                    1. DDavid Simpson @David_Simpson
                        2024-04-16 11:18:03.794Z

                        Glad to hear it's working for you!

                        Would you be able to update the script to 2.3.2 and then let me know what the error is when it tries to recall the track sizes?

                        Cheers!
                        David

                        1. TT Michaels @T_Michaels
                            2024-04-16 11:25:12.359Z

                            Hey David,

                            2.3.2 installed on latest Soundflow, PT2023.9 on Big Sur

                            "The command, "84", is not compatible with this version of Pro Tools. (PT_UnknownError) (Bounce Tracks: Line 125)"

                            1. DDavid Simpson @David_Simpson
                                2024-04-16 11:37:15.787Z

                                Thank you!

                                Should be v2.3.3 up now. Let me know if that fixes it at all.

                                Cheers!
                                David

                                1. TT Michaels @T_Michaels
                                    2024-04-16 12:10:35.494Z

                                    On 2023.9 it bounces but when it's restoring the window config it hides the bounced tracks at the end, no error message but no popup window saying complete.

                                    Works normally on 2024.3

                                    1. DDavid Simpson @David_Simpson
                                        2024-04-16 13:17:28.291Z

                                        Cool, was this happening on the previous version of the script too? - v2.3.2

                                        1. TT Michaels @T_Michaels
                                            2024-04-16 13:55:52.189Z

                                            Nope was still doing the bounce but had the error message once finished and putting the window config back, but it wasn't hiding tracks.

                                            1. DDavid Simpson @David_Simpson
                                                2024-04-16 14:18:48.941Z

                                                Does it manage to create a memory location and call it "Track Visibility"?