Select Tracks Inside Folder in Logic
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 })
- 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. :-)
- NNicolas Aparicio @Nicolas_Aparicio
Thanks legend, I'll keep having a look as well 🤙
- NIn reply toNicolas_Aparicio⬆:Nicolas Aparicio @Nicolas_Aparicio
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" })
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. :-)
- NNicolas Aparicio @Nicolas_Aparicio
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 })
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 :-)
- NNicolas Aparicio @Nicolas_Aparicio
Sure thing, thanks a lot Kitch
- NIn reply toNicolas_Aparicio⬆:Nicolas Aparicio @Nicolas_Aparicio
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