No internet connection
  1. Home
  2. How to

Find all "yellow" (used) outputs from session and present in popupsearch

By Forrester Savell @Forrester_Savell
    2023-09-24 22:26:53.076Z2023-09-24 23:45:37.161Z

    I'm trying to create a list of all outputs (busses or physical) that are actively used in a session, followed by presenting those in a popupsearch dialog for selection.
    I've managed to use both Search track IO and Scrape a track's output? #post-4 to successfully create a list of Outputs and list them in popupsearch, however there are multiple entries for the same outputs using both these methods.

    I've tried a few ways to filter the array but can't seem to remove the duplicates.
    Can anyone please suggest an efficient way to grab all the "yellow" outputs used in the session and present them as written in a popupsearch, or alternatively let me know where I'm going wrong with trying to remove duplicates from the trackObj output array

    Thanks!

    Here's the code that works, but the filtering method I'm using doesn't appear to remove duplicate output entries

    sf.ui.proTools.appActivateMainWindow();
    sf.ui.proTools.mainWindow.invalidate();
    
    const trackNames = sf.ui.proTools.selectedTrackNames;
    
    let trackObj = [];
    
    trackNames.map(track => {
        sf.ui.proTools.trackSelectByName({ names: [track] });
    
        const audioIO = sf.ui.proTools.selectedTrack.groups.whoseTitle.is('Audio IO');
        const inputBtn = audioIO.first.popupButtons.whoseTitle.startsWith('Audio Input Path selector').first;
        const outputBtn = audioIO.first.popupButtons.whoseTitle.startsWith('Audio Output Path selector').first;
    
        //let input;
        let output;
    
        /*if (inputBtn.exists) {
            input = inputBtn.title.invalidate().value.split('\n').pop();
        } else {
            input = 'No Input Available';
        }*/
    
        if (outputBtn.exists) {
            output = outputBtn.title.invalidate().value.split('\n').pop();
        } else {
            output = 'No Output Available';
        }
        //log(output)
        trackObj.push({
            //name: track,
            //input: input,
            //output: 
            output,
        })
    });
    //log(trackObj);
    
    let uniqueOutputs = trackObj.filter((item,
        index) => trackObj.indexOf(item) === index);
    
    log (uniqueOutputs)
    
    let findOutput = JSON.parse(sf.interaction.popupSearch({
    
        title: "Which Output would you like to add to?",
        items: uniqueOutputs.map(tr => ({
            name: JSON.stringify(tr),
        }))
    }).item.name);
    
    
    
    
    //alert(findOutput);
    
    
    Solved in post #5, click to view
    • 18 replies

    There are 18 replies. Estimated reading time: 18 minutes

    1. Kitch Membery @Kitch2023-09-25 01:16:52.672Z

      Hi @Forrester_Savell

      Unfortunately, SoundFlow is unable to identify the yellow items in the menu.

      To remove duplicates items in an array you can use new Set

      const allCollectedOutputs = [
        "Output 1",
        "Output 1",
        "Output 1",
        "Output 1",
        "Output 1",
        "Output 2",
        "Output 1",
        "Output 3",
        "Output 4",
        "Output 3",
        "Output 4",
        "Output 5",
        "Output 6",
        "Output 6",
        "Output 6",
        "Output 7",
        "Output 7",
        "Output 7",
        "Output 7",
        "Output 7",
      ];
      
      const uniqueOutputs = [...new Set(allCollectedOutputs)];
      
      console.log(uniqueOutputs);
      

      I hope that helps. :-)

      1. FForrester Savell @Forrester_Savell
          2023-09-25 01:31:37.338Z

          Thanks @Kitch

          For some reason, none of the remove duplicate methods I've tried remove the duplicate outputs listed in trackObj, including the one you've just suggested.

          I've tried these:

          function onlyUnique(value, index, self) {
            return self.indexOf(value) === index;
          }
          
          let uniqueOutputs = trackObj.filter(onlyUnique);
          
          function removeDuplicates(array){
              return array.filter((el, index) => array.indexOf(el) ===index);
          }
          
          log (removeDuplicates(trackObj))
          

          but when I select 7 tracks all with the same output and then run the script, the new array still lists all 7 items. eg. If i run the below code:

          sf.ui.proTools.appActivateMainWindow();
          sf.ui.proTools.mainWindow.invalidate();
          
          const trackNames = sf.ui.proTools.selectedTrackNames;
          
          let trackObj = [];
          
          trackNames.map(track => {
              sf.ui.proTools.trackSelectByName({ names: [track] });
          
              const audioIO = sf.ui.proTools.selectedTrack.groups.whoseTitle.is('Audio IO');
              const inputBtn = audioIO.first.popupButtons.whoseTitle.startsWith('Audio Input Path selector').first;
              const outputBtn = audioIO.first.popupButtons.whoseTitle.startsWith('Audio Output Path selector').first;
          
              //log(outputBtn)
          
              //let input;
              let output;
          
              /*if (inputBtn.exists) {
                  input = inputBtn.title.invalidate().value.split('\n').pop();
              } else {
                  input = 'No Input Available';
              }*/
          
              if (outputBtn.exists) {
                  output = outputBtn.title.invalidate().value.split('\n').pop();
              } else {
                  output = 'No Output Available';
              }
              //log(output)
              trackObj.push({
                  //name: track,
                  //input: input,
                  //output: 
                  output,
              })
          });
          
          
          const uniqueOutputs = [...new Set(trackObj)];
          
          log(uniqueOutputs);
          

          I get [
          {
          "output": "KICK (Stereo)"
          },
          {
          "output": "KICK (Stereo)"
          },
          {
          "output": "KICK (Stereo)"
          },
          {
          "output": "KICK (Stereo)"
          },
          {
          "output": "KICK (Stereo)"
          },
          {
          "output": "KICK (Stereo)"
          },
          {
          "output": "KICK (Stereo)"
          }
          ]

          1. Kitch Membery @Kitch2023-09-25 01:32:38.609Z

            Just doing a refactor of your script now. you're returning an array of objects rather than just an array. Stand by :-)

            1. Kitch Membery @Kitch2023-09-25 01:35:59.857Z

              Untested, but this should do the trick. :-)

              sf.ui.proTools.appActivateMainWindow();
              sf.ui.proTools.mainWindow.invalidate();
              
              const trackNames = sf.ui.proTools.selectedTrackNames;
              
              const trackOutputs = trackNames.map(trackName => {
                  let track = sf.ui.proTools.trackGetByName({ name: trackName }).track;
              
                  const audioIO = track.groups.whoseTitle.is('Audio IO');
                  const outputBtn = audioIO.first.popupButtons.whoseTitle.startsWith('Audio Output Path selector').first;
              
                  let output = outputBtn.exists ? outputBtn.title.invalidate().value.split('\n').pop() : 'No Output Available';
                  return output;
              });
              
              // @ts-ignore
              const uniqueOutputs = [...new Set(trackOutputs)];
              
              const selectedOutput = sf.interaction.popupSearch({
                  title: "Which Output would you like to add to?",
                  items: uniqueOutputs.map(output => ({ name: output })),
              }).item.name;
              
              log(selectedOutput);
              

              PS. Hello from, Kingscliff! ;-)

              ReplySolution
              1. FForrester Savell @Forrester_Savell
                  2023-09-25 01:45:21.505Z

                  So elegant, thanks Kitch. Can you break it down what was going wrong in my original code (if you have time!), still learning here.

                  PS. Ha, i'm literally a few suburbs away, Reedy Creek.

                  1. Kitch Membery @Kitch2023-09-25 02:23:43.487Z

                    Other Changes I made were...


                    Instead of selecting each track one by one

                    sf.ui.proTools.trackSelectByName({ names: [track] });
                    

                    to then get the output of the selected track, I instead used...

                    let track = sf.ui.proTools.trackGetByName({ name: trackName }).track;
                    

                    This avoids the need for each track to be selected. :-)


                    The map method in Javascript returns a new array so there is no need to declare the trackObj (that I renamed trackOutputs for clarity) outside the map function. Instead, I assigned the variable name directly to the map method.
                    i.e.

                    const trackOutputs = trackNames.map(...
                    

                    You'd then change this

                        trackObj.push({
                            output,
                        })
                    

                    to this.

                        return {
                            output,
                        }
                    

                    (This will still returns an object in turn creating an array of objects)


                    I then changed what you were pushing to the trackObj. You were originally pushing an object {} rather than just a single output on each iteration of the map function.

                    so this...

                        trackObj.push({
                            output,
                        })
                    

                    I changed to this...

                    return output;
                    

                    Let me know if you have more questions about this, am happy to answer them.

                    1. Kitch Membery @Kitch2023-09-25 02:40:16.232Z

                      You could also use the filter method instead to get unique track outputs like this.

                      // Use filter to find unique outputs
                      const uniqueOutputs = trackOutputs.filter((item, index, arr) => arr.indexOf(item) === index);
                      

                      I prefer the new Set way though as it's cleaner.

                      1. FForrester Savell @Forrester_Savell
                          2023-09-25 03:32:15.302Z

                          All this is really helpful, thanks again @Kitch

                      2. In reply toKitch:
                        Kitch Membery @Kitch2023-09-25 02:01:17.893Z

                        Sure can,

                        Your script was creating an array of objects like this.

                        [
                          {
                            output: "MacBook Pro Speakers 1-2 (Stereo) -> Main",
                          },
                          {
                            output: "Bus 3-4 (Stereo) - track inactive",
                          },
                          {
                            output: "Bus 3-4 (Stereo) - track inactive",
                          },
                          {
                            output: "Bus 3-4 (Stereo) - track inactive",
                          },
                        ];
                        

                        But the filter method you were using was expecting an array of strings.

                        [
                          "MacBook Pro Speakers 1-2 (Stereo) -> Main",
                          "Bus 3-4 (Stereo) - track inactive",
                          "Bus 3-4 (Stereo) - track inactive",
                          "Bus 3-4 (Stereo) - track inactive",
                        ];
                        
                      3. Kitch Membery @Kitch2023-09-25 01:43:51.408Z

                        Let me know if this suits the context of what you're trying to achieve, as it seems you may be wanting functionality that the refactored script may not cater for. (ie the input paths)

                        1. FForrester Savell @Forrester_Savell
                            2023-09-26 03:00:46.094Z

                            Hey @Kitch

                            I have 2 follow up questions, related to the above.

                            When finding the trackOutputs, I ended up using this, as it presents cleanly in the popupmenu :

                            track.outputPathButton.value.invalidate().value
                            

                            While I can see using console.log how it is truncating the output name to what is visually shown on the Edit Window, I don't really know what the value.invalidate().value part is doing or when to use it?

                            Following that, is there a way to capture all the outputs in a session such as:

                                const outputPaths = trackOutput.popupMenuFetchAllItems().menuItems;
                            

                            but list just the truncated "visible" names (like the above example that uses value.invalidate().value) in another popupsearch, without all the noise I can see when I log 'outputPaths'.

                            Essentially want to provide a list of all the session outputs/busses, to be chosen by user, to either Add or Replace the first chosen output.

                            1. FForrester Savell @Forrester_Savell
                                2023-09-26 08:29:51.394Z

                                FWIW I came up with the below. Its close but not quite there.

                                let busOutputs = sf.ui.proTools.selectedTrack.outputPathButton.popupMenuFetchAllItems({ dismissMenu: true, }).menuItems
                                    .map(outputs => outputs['Names'])
                                    .filter(busses => busses.includes('bus',))
                                    .map(trimName => trimName[2].replace(/^(\s+)/, '').replace(/( \(\S+\))/, ''))
                                
                                let physicalOutputs = sf.ui.proTools.selectedTrack.outputPathButton.popupMenuFetchAllItems({ dismissMenu: true, }).menuItems
                                    .map(outputs => outputs['Names'])
                                    .filter(outputs => outputs.includes('output'))
                                    .map(trimName => trimName[1].replace(/^(\s+)/, '').replace(/( \(\S+\))/, ''));
                                
                                let combinedOutputs = [busOutputs, ...physicalOutputs];
                                
                                
                                //log(combinedOutputs)
                                const selectedOutput = sf.interaction.popupSearch({
                                        title: "Which Output would you like to add to?",
                                        items: combinedOutputs.map(output => ({ name: output })),
                                    }).item.name;
                                
                                
                                1. Kitch Membery @Kitch2023-09-26 12:11:00.921Z

                                  Nice one @Forrester_Savell

                                  I assume you want to get any output that starts with "bus" or "output". And then have the popup return the user-selected rows' output path?

                                  To do that you could do something like this.

                                  sf.ui.proTools.appActivateMainWindow();
                                  
                                  const menuItems = sf.ui.proTools.selectedTrack.outputPathButton.popupMenuFetchAllItems({ dismissMenu: true, }).menuItems
                                  const outputPaths = menuItems
                                      .filter(outputs => outputs.path[0] === "bus" || outputs.path[0] === "output")
                                      .map(output => ({
                                          name: output.path.join(" → "),
                                          path: output.path,
                                      }));
                                  
                                  const selectedOutputPath = sf.interaction.popupSearch({
                                      title: "Which Output would you like to add to?",
                                      items: outputPaths,
                                  }).item.path;
                                  
                                  log(selectedOutputPath);
                                  

                                  Let me know if that works for you. :-)

                                  1. FForrester Savell @Forrester_Savell
                                      2023-09-26 21:33:59.426Z

                                      Hey @Kitch

                                      Awesome, I was originally trying to use the OR method, but couldn't work out how to combine the arrays. Thanks for this.

                                      So I think this works, however I'm having the issue where the popupsearch menu disappears as soon as it's opened, even when I run your code in an isolated script, I still get the same issue. It's weird as the first popupsearch works fine (from the first code in this post).

                                      I can see that someone else had the issue Soundfow search menu disappears

                                      Any thoughts on this problem?

                                      1. Kitch Membery @Kitch2023-09-26 23:10:34.540Z

                                        Try invalidating the main window of Pro Tools. This will make sure the track information is refreshed, which may be the issue.

                                        sf.ui.proTools.appActivateMainWindow();
                                        sf.ui.proTools.mainWindow.invalidate();
                                        
                                        const menuItems = sf.ui.proTools.selectedTrack.outputPathButton.popupMenuFetchAllItems({ dismissMenu: true, }).menuItems
                                        const outputPaths = menuItems
                                            .filter(outputs => outputs.path[0] === "bus" || outputs.path[0] === "output")
                                            .map(output => ({
                                                name: output.path.join(" → "),
                                                path: output.path,
                                            }));
                                        
                                        const selectedOutputPath = sf.interaction.popupSearch({
                                            title: "Which Output would you like to add to?",
                                            items: outputPaths,
                                        }).item.path;
                                        
                                        log(selectedOutputPath);
                                        

                                        If that does not work, it would be great to get a screen recording of it happening. :-)

                                        1. FForrester Savell @Forrester_Savell
                                            2023-10-01 18:11:39.136Z

                                            Just updating this thread with the solution to disappearing popupseach, in case anyone stumbles upon it. Turns out it seems to be isolated to Catalina OS. Removing ({dismissMenu: true, }) solves it.
                                            See here: Popupsearch disappears #post-9

                                        2. In reply toKitch:
                                          FForrester Savell @Forrester_Savell
                                            2024-06-17 08:34:46.789Z

                                            Hey @Kitch

                                            How would I modify this so the list of outputs doesn't include the full path info (i.e. bus -> bus menu 1-128 -> name of bus), and only shows the name of the output irrespective if it is an output or bus?

                                            I've tried changing the mapping function but I get errors when the popupSearch window appears.

                                            if (!sf.ui.proTools.isRunning) throw `Pro Tools is not running`;
                                            
                                            sf.ui.proTools.appActivateMainWindow();
                                            sf.ui.proTools.mainWindow.invalidate();
                                            
                                            function outputSearch() {
                                                let outputPathButton = sf.ui.proTools.selectedTrack.outputPathButton;
                                                // Fetch all menu items
                                                let items = outputPathButton.popupMenuFetchAllItems().menuItems.map(function (item) {
                                                    return {
                                                        name: item.names,//.join(' > '),
                                                        //path: item.names
                                                    };
                                                });
                                                log (items)
                                            
                                                let filteredResults = items.filter(function (str) { return !str.name.includes('track'); });
                                            
                                                // Search
                                                let chosenPath = sf.interaction.popupSearch({
                                                    items: filteredResults,
                                                    title: 'Search for a Track Output',
                                                }).item.path;
                                            
                                                //Get focus back
                                                sf.wait({ intervalMs: 100 });
                                                sf.ui.proTools.appActivate();
                                                sf.wait({ intervalMs: 50 });
                                            
                                                // Select Chosen Path
                                                sf.ui.proTools.selectedTrack.trackOutputSelect({ outputPath: chosenPath, selectForAllSelectedTracks: true });
                                            
                                                sf.ui.proTools.invalidate();
                                            }
                                            
                                            outputSearch();
                                            
                                            1. Chad Wahlbrink @Chad2024-06-17 14:43:24.824Z

                                              @Forrester_Savell, I responded on this topic on this thread:
                                              Popupsearch disappears #post-13

                                              Thanks for the patience! Happy Scripting! ✨✨