No internet connection
  1. Home
  2. How to

Select Tracks Inside Folder in Logic

By Nicolas Aparicio @Nicolas_Aparicio
    2024-10-09 23:29:10.252Z

    Hi team,
    I was wondering if there is a better way to select the tracks inside a folder in Logic than what I've done here. This one works but stops if it finds a subfolder in it.
    I've tried positions of elements like the solo and mute button etc but I was wondering if there is an actual "ax" value, description etc, that could tell which track is folder or subfolder?
    @Kitch maybe you can point me in the right direction?

    if (!sf.ui.logic.isRunning) throw `Logic Pro is not running`;
    
    sf.ui.logic.invalidate();
    
    const selectedTrack = ({
        Name: sf.ui.logic.selectedTrack.getString("AXDescription").match(/“(.*)”/)[1],
        Number: sf.ui.logic.selectedTrack.getString("AXDescription").match(/^[^\d]*(\d+)/)[1],
    });
    
    const allTracks = sf.ui.logic.visibleTracks.map(track => ({
        Name: track.getString("AXDescription").match(/“(.*)”/)[1],
        Number: track.getString("AXDescription").match(/^[^\d]*(\d+)/)[1],
        IsFolder: track.getFirstOf("AXDisclosureTriangle").exists,
    }));
    
    let parentFolder = []
    for (let i = 0; i < allTracks.length; i++) {
        if (allTracks[i].IsFolder && allTracks[i].Number <= selectedTrack.Number) {
            parentFolder.push(allTracks[i])
        };
    };
    
    let folderBelow = []
    for (let i = 0; i < allTracks.length; i++) {
        if (allTracks[i].IsFolder && allTracks[i].Number > parentFolder.slice(-1)[0].Number) {
            folderBelow.push(allTracks[i])
        };
    };
    
    let tracksInsideFolder = []
    for (let i = 0; i < allTracks.length; i++) {
        if (allTracks[i].Number > parentFolder.slice(-1)[0].Number && allTracks[i].Number < folderBelow[0].Number) {//> to deselect parent folder >= to select it as well
            tracksInsideFolder.push(allTracks[i].Name)
        };
    };
    
    sf.ui.logic.trackSelectByName({ names: tracksInsideFolder })
    
    • 8 replies
    1. Kitch Membery @Kitch2024-10-10 02:25:54.541Z

      I'll have to take a look when I have a moment to see if I can find a way. Hopefully, there is a solution. :-)

      1. NNicolas Aparicio @Nicolas_Aparicio
          2024-10-10 03:37:46.314Z

          Thanks legend, I'll keep having a look as well 🤙

        • N
          Nicolas Aparicio @Nicolas_Aparicio
            2024-10-11 07:17:50.296Z

            Right, I've found a way that works and selects all tracks inside the folder (including sub-folders) though it selects all tracks below if it's the last available folder, which I'll figure out later.
            I ended up using the position of the folder's triangle to determine if it is Folder or Sub-Folder.
            This one works great for now but I'm pretty sure there is a cleaner way of doing this, keen on seeing what you come up with Kitch!

            if (!sf.ui.logic.isRunning) throw `Logic Pro is not running`;
            
            sf.ui.logic.invalidate();
            
            const selectedTrackNumber = sf.ui.logic.selectedTrack.getString("AXDescription").match(/^[^\d]*(\d+)/)[1]
            
            const allTracks = []
            const folderValues = []
            sf.ui.logic.visibleTracks.forEach(track => {
                let name = track.getString("AXDescription").match(/“(.*)”/)[1]
                let number = track.getString("AXDescription").match(/^[^\d]*(\d+)/)[1]
            
                if (track.getFirstOf("AXDisclosureTriangle").exists) {
                    let isFolder = track.getFirstOf("AXDisclosureTriangle").exists
                    let folderPosition = track.getFirstOf("AXDisclosureTriangle").position.x
            
                    allTracks.push({ Name: name, Number: number, IsFolder: isFolder, FolderPosition: folderPosition })
                    folderValues.push(folderPosition)
            
                } else {
                    allTracks.push({ Name: name, Number: number })
                };
            });
            
            const mainFolderPosition = Math.min(...folderValues)
            
            let parentFolder = []
            for (let i = 0; i < allTracks.length; i++) {
                if (allTracks[i].IsFolder && allTracks[i].Number <= selectedTrackNumber && allTracks[i].FolderPosition === mainFolderPosition) {
                    parentFolder.push(allTracks[i])
                };
            };
            
            let folderBelow = []
            for (let i = 0; i < allTracks.length; i++) {
                if (allTracks[i].IsFolder && allTracks[i].Number > parentFolder.slice(-1)[0].Number && allTracks[i].FolderPosition === mainFolderPosition) {
                    folderBelow.push(allTracks[i])
                };
            };
            
            let tracksInsideFolder = []
            for (let i = 0; i < allTracks.length; i++) {
                if (folderBelow.length !== 0) {
                    //> to deselect parent folder >= to select it as well
                    if (allTracks[i].Number > parentFolder.slice(-1)[0].Number && allTracks[i].Number < folderBelow[0].Number) {
                        tracksInsideFolder.push(allTracks[i].Name)
                    };
                } else {
                    //> to deselect parent folder >= to select it as well
                    if (allTracks[i].Number > parentFolder.slice(-1)[0].Number) {
                        tracksInsideFolder.push(allTracks[i].Name)
                    };
                };
            
            };
            
            sf.ui.logic.trackSelectByName({ names: tracksInsideFolder, identicalNames: "SelectAll" })
            
            1. Kitch Membery @Kitch2024-10-21 20:06:26.383Z

              Great stuff, that's the approach I would have taken. I'll circle back to this when I have some time and see if I can improve/simplify it, as I'm sure others will request this in the future. :-)

              1. NNicolas Aparicio @Nicolas_Aparicio
                  2024-10-21 23:14:06.761Z

                  Hi @Kitch, cheers mate, I've finally got to this;
                  It selects all tracks inside the folder and does a quick check to select the right tracks if it's the last folder in the session.
                  For now I've made it work for all first level folders but haven't found a way to do it with second level ones, as I haven't found a way to distinguish second level from first level tracks .
                  The code also works if the starting point is any track inside the folder hence I've left a "redundant" code in it.

                  Here is what I've got so far:

                  function selectTracksInsideFolder({ folderName, includeFolder }) {
                  
                      function checkFolderState() {
                  
                          sf.ui.logic.visibleTracks.forEach(track => {
                              let name = track.getString("AXDescription").match(/“(.*)”/)[1]
                              let folderTriangle = track.getFirstOf("AXDisclosureTriangle")
                              let isFolder = folderTriangle.exists
                              let isFolderOpen = folderTriangle.isCheckBoxChecked
                              if (isFolder && !isFolderOpen && name === folderName) {
                                  sf.ui.logic.trackSelectByName({ names: [folderName] })
                                  folderTriangle.mouseClickElement();
                              };
                          });
                          sf.ui.logic.invalidate();
                  
                      };
                  
                      function selectTracks() {
                  
                          function findOutsideTracks() {
                  
                              if (folderBelow.length === 0) {
                                  sf.ui.logic.trackSelectByName({ names: [folderName] })
                  
                                  let selectedFolderTrack = sf.ui.logic.selectedTrack.getFirstOf("AXDisclosureTriangle")
                                  if (selectedFolderTrack.exists && selectedFolderTrack.isCheckBoxChecked) {//Close Folder
                                      selectedFolderTrack.mouseClickElement();
                                  };
                  
                                  sf.ui.logic.invalidate();
                                  sf.ui.logic.appActivate();
                  
                                  let tracksOutsideLastFolder = []
                                  sf.ui.logic.visibleTracks.forEach(track => {
                                      let name = track.getString("AXDescription").match(/“(.*)”/)[1]
                                      let number = track.getString("AXDescription").match(/^[^\d]*(\d+)/)[1]
                                      if (+number > +selectedTrackNumber) {
                                          tracksOutsideLastFolder.push(name)
                                      };
                                  });
                  
                                  selectedFolderTrack.mouseClickElement();
                  
                                  return tracksOutsideLastFolder
                              };
                  
                          };
                  
                          const allTracks = []
                          const folderValues = []
                  
                          sf.ui.logic.visibleTracks.forEach(track => {
                              let name = track.getString("AXDescription").match(/“(.*)”/)[1]
                              let number = track.getString("AXDescription").match(/^[^\d]*(\d+)/)[1]
                  
                              if (track.getFirstOf("AXDisclosureTriangle").exists) {
                                  let isFolder = track.getFirstOf("AXDisclosureTriangle").exists
                                  let isFolderOpen = track.getFirstOf("AXDisclosureTriangle").isCheckBoxChecked
                                  let folderPosition = track.getFirstOf("AXDisclosureTriangle").position.x
                  
                                  allTracks.push({ Name: name, Number: number, IsFolder: isFolder, FolderPosition: folderPosition, IsFolderOpen: isFolderOpen })
                                  folderValues.push(folderPosition)
                  
                              } else {
                                  allTracks.push({ Name: name, Number: number })
                              };
                          });
                  
                          let mainFolderPosition = Math.min(...folderValues)
                  
                          let parentFolder = []
                          for (let i = 0; i < allTracks.length; i++) {
                              if (allTracks[i].IsFolder && +allTracks[i].Number <= +selectedTrackNumber && allTracks[i].FolderPosition === mainFolderPosition) {
                                  parentFolder.push(allTracks[i])
                              };
                          };
                  
                          let folderBelow = []
                          for (let i = 0; i < allTracks.length; i++) {
                              if (allTracks[i].IsFolder && +allTracks[i].Number > +parentFolder.slice(-1)[0].Number && allTracks[i].FolderPosition === mainFolderPosition) {
                                  folderBelow.push(allTracks[i])
                              };
                          };
                  
                          let tracksInsideFolder = []
                          for (let i = 0; i < allTracks.length; i++) {
                  
                              if (includeFolder === true) {
                                  if (folderBelow.length !== 0) {
                                      if (+allTracks[i].Number >= +parentFolder.slice(-1)[0].Number && +allTracks[i].Number < +folderBelow[0].Number) {//>= to select parent folder
                                          tracksInsideFolder.push(allTracks[i].Name)
                                      };
                                  } else {
                                      if (+allTracks[i].Number >= +parentFolder.slice(-1)[0].Number) {//for last folder
                                          tracksInsideFolder.push(allTracks[i].Name)
                                      };
                                  };
                              };
                              if (includeFolder === false) {
                                  if (folderBelow.length !== 0) {
                                      if (+allTracks[i].Number > +parentFolder.slice(-1)[0].Number && +allTracks[i].Number < +folderBelow[0].Number) {//> to deselect parent folder
                                          tracksInsideFolder.push(allTracks[i].Name)
                                      };
                                  } else {
                                      if (+allTracks[i].Number > +parentFolder.slice(-1)[0].Number) {//for last folder
                                          tracksInsideFolder.push(allTracks[i].Name)
                                      };
                                  };
                              };
                  
                          };
                  
                          if (folderBelow.length === 0) {//for last folder
                              let tracksOutsideLastFolder = findOutsideTracks();
                              sf.ui.logic.trackSelectByName({ names: tracksInsideFolder.filter(i => !tracksOutsideLastFolder.includes(i)), identicalNames: "SelectAll" })
                  
                          } else {
                              sf.ui.logic.trackSelectByName({ names: tracksInsideFolder, identicalNames: "SelectAll" })
                          };
                  
                      };
                  
                      if (!sf.ui.logic.isRunning) throw `Logic Pro is not running`;
                  
                      sf.ui.logic.appActivateMainWindow();
                      sf.ui.logic.invalidate();
                  
                      const intendedFolder = sf.ui.logic.visibleTracks.map(t => t.getString("AXDescription")).filter(t => t.includes(folderName))[0]
                      const selectedTrackNumber = intendedFolder.match(/^[^\d]*(\d+)/)[1]
                  
                      checkFolderState();
                      selectTracks();
                  
                  };
                  
                  selectTracksInsideFolder({ folderName: folderName, includeFolder: false })
                  
                  1. Kitch Membery @Kitch2024-10-21 23:24:30.027Z

                    Great progress... This is helpful!

                    Please bump this thread next week if I don't get back to it :-)

                    1. NNicolas Aparicio @Nicolas_Aparicio
                        2024-10-21 23:40:03.045Z

                        Sure thing, thanks a lot Kitch

                • N
                  Nicolas Aparicio @Nicolas_Aparicio
                    2025-04-29 12:58:13.021Z2025-04-29 21:06:59.056Z

                    Hi @Kitch, I've re-written this one and created a shorter/better code, though, not ideal for the last folder in the session.
                    I'll try and work one out based of the outputs/routing in the mixer window to make it more robust but hopefully this one will be handy to someone.

                    /**
                     * @param {Object} args
                     * @param {string} args.folderName
                     * @param {boolean} args.includeFolder
                     */
                    function selectTracksInsideFolder({ folderName, includeFolder }) {
                    
                        function getTracksInfo() {
                            sf.ui.logic.invalidate();
                            sf.ui.logic.appActivate();
                    
                            const tracksInfo = sf.ui.logic.visibleTracks.map(track => {
                                const trackDescription = track.getString("AXDescription");
                                const name = trackDescription !== null ? trackDescription.match(/“(.*)”/)[1] : undefined;
                                const number = trackDescription !== null ? +trackDescription.match(/^[^\d]*(\d+)/)[1] : undefined;
                                const folderTriangle = track.children.whoseRole.is("AXDisclosureTriangle");
                                const isFolder = folderTriangle.exists;
                                const isFolderOpen = folderTriangle.first.isCheckBoxChecked;
                    
                                return {
                                    name,
                                    number,
                                    folderTriangle,
                                    isFolder,
                                    isFolderOpen,
                                }
                            }).filter(i => Boolean(i.name));
                    
                            return tracksInfo;
                        }
                    
                        let tracksInfo = getTracksInfo();
                    
                        const targetFolder = tracksInfo.filter(t => t.name.includes(folderName))[0];
                        const allFolders = tracksInfo.filter(t => t.isFolder);
                        const targetIndex = allFolders.findIndex(folder => folder.name.includes(folderName));
                        const nextFolder = allFolders[targetIndex + 1] !== undefined ? allFolders[targetIndex + 1] : tracksInfo.slice(-1)[0];
                    
                        if (!targetFolder.isFolderOpen) {
                            sf.ui.logic.trackSelectByName({ names: [folderName] });
                            targetFolder.folderTriangle.invalidate().first.mouseClickElement({ onError: 'Continue' });
                            tracksInfo = getTracksInfo();
                        }
                    
                        const tracksToSelect = includeFolder
                            ? tracksInfo.filter(t => t.number >= targetFolder.number && t.number < nextFolder.number)
                            : tracksInfo.filter(t => t.number > targetFolder.number && t.number < nextFolder.number);
                    
                        sf.ui.logic.trackSelectByName({ names: tracksToSelect.map(t => t.name), identicalNames: 'SelectAll' });
                    }
                    

                    Cheers