No internet connection
  1. Home
  2. Script Sharing

How to loop through all selected tracks and repeat an action for each track

Here's a script that showcases how to loop through all the selected tracks, selecting each of them in turn and performing an action for every selected track, and then finally restoring the selection when done.


function doForAllSelectedTracks(action) {
    var originallySelectedTrackNames = sf.ui.proTools.selectedTrackNames;

    try {
        sf.ui.proTools.selectedTrackHeaders.forEach(track => {
            track.trackSelect();
            action(track);
        });
    }
    finally {
        sf.ui.proTools.trackSelectByName({ names: originallySelectedTrackNames });
    }
}

/**@param {AxPtTrackHeader} track */
function trackFunc(track) {

    //Insert your code here
    log(track.normalizedTrackName);

}

doForAllSelectedTracks(trackFunc);
  • 13 replies
  1. Just out of curiosity, is there an advantage to this method?
    I've been using this:

    var originallySelectedTrackNames = sf.ui.proTools.selectedTrackNames.slice()
    for (var i in originallySelectedTrackNames) {
         sf.ui.proTools.trackSelectByName({
              names: [originallySelectedTrackNames[i]],
          });
          //Your Code Here
          log(originallySelectedTrackNames[i])
    }
    sf.ui.proTools.trackSelectByName({ names: originallySelectedTrackNames });
    
    1. The try/finally block that I'm using ensures that even if an error occurs, the original track will be selected again.
      It also has the benefit of extracting the logic of actually enumerating through the array into a function, which cleans up the code and makes it more reusable.
      It's generally a best practice to try to extract logic out into separate, reusable functions. This is how SoundFlow is coded internally as well. Reusing code ensures bug fixes can be applied across all uses of that code. It also helps make complex scripts more readable and maintainable, overall reducing the number of bugs that get created in the first place.

      1. An important concept that we use in software design is that it's vital to "encapsulate complexity". In this case, the complexity is that you're just wanting to enumerate through some tracks, but you don't want that temporary state change (the difference in track selection) to leak out of that function. By wrapping it up in a function, we've encapsulated the complexity of dealing with any errors occurring while the temp state is in place.
        Any callers of the function will now not have to deal with the complexity - they can just deal with what they were trying to do.
        This concept is hugely important when writing complex apps. Always try to reduce complexity by encapsulating it in functions with small API surfaces.

        1. Another thing I'd say about your code, is that looping with for..in over an array is not desirable.

          See this quote from Mozillas Javascript documentation:

          https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in

          If you do want to use a for loop on an array, instead use a for...of loop.

          https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of
    2. In reply tochrscheuer:

      Fantastic. Thanks for the thorough explanation. I’m learning this as I go and have been revisiting older scripts I’ve written to clean them up. That being said, using for loops is easier for me to read. I just have get used to doing it this way.

      1. You can still use a for loop, but use for...of instead of for...in (for of also has the benefit of directly giving you the value), or use a regular for loop with indices.

        1. The for of loop would look like this:

          var originallySelectedTrackNames = sf.ui.proTools.selectedTrackNames.slice();
          for (var name of originallySelectedTrackNames) {
               sf.ui.proTools.trackSelectByName({
                    names: [name],
                });
                //Your Code Here
                log(name);
          }
          sf.ui.proTools.trackSelectByName({ names: originallySelectedTrackNames });
          

          Note that this doesn't cover the try/finally case, but just shows the specific way to use for...of instead of for...in

          But yea, I'd still recommend the method I used in the original post, as it down the line will help make your scripts more stable.

      2. A
        In reply tochrscheuer:
        Alex Oldroyd @Alex_Oldroyd8
          2022-04-09 18:12:06.821Z

          Hi Christian

          How would you go about converting this to Logic Pro X?

          Thanks

          Alex

          1. Hi Alex,

            Unfortunately, I don't know much about how Logic operates. It would be better to ask that as a new question in the How to section :)

          2. In reply tochrscheuer:
            Mitch Willard @Mitch_Willard_the2nd
              2022-04-29 01:24:25.010Z

              Hi @chrscheuer ,

              When I use this code in other scripts, it seems to skip the 2nd selected track then apply the code to the next following track after the last selected.

              eg.
              If the selected tracks are:
              Audio 1
              Audio 2
              Audio 3

              The action will apply the code either to:
              Audio 1
              Audio 3
              Audio 4

              or in some cases:
              Audio 1 twice
              Audio 3

              Is it something I'm missing? or messing up? Here is just a basic code which is giving me the issue when applying, as oppose to sharing the entire code, which I can if need be.

              function doForAllSelectedTracks(action) {
                  /// Get Selected track names to variable
                  globalState.selectedTracks = sf.ui.proTools.selectedTrackNames
              
                  try {
                      sf.ui.proTools.selectedTrackHeaders.forEach(track => {
                          track.trackSelect();
                          action(track);
                      });
                  }
                  finally {
                      sf.ui.proTools.trackSelectByName({ names: globalState.selectedTracks });
                  }
              };
              
              /* bounce loop */
              function bounceFunc() {
              
                  sf.ui.proTools.menuClick({
                      menuPath: ['Edit', 'Consolidate Clip'],
                  })
              
              };
              
              doForAllSelectedTracks(bounceFunc);
              
              
              1. Hi Mitch,

                On the surface, this looks correct. Could you open a new issue with bit.ly/sfscripthelp so we can track this separately?

                1. Mitch Willard @Mitch_Willard
                    2022-04-30 12:22:27.072Z

                    Will do @chrscheuer . Thanks

                2. T
                  In reply tochrscheuer:
                  Thomas Gloor @Thomas_Gloor
                    2022-04-30 16:27:01.498Z

                    Hey @chrscheuer
                    Thank you for this. One little question. Would it be possible to have it work for every selected track EXCEPT the first one?

                    Thanks!