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);
- Kitch Membery @Kitch2023-09-25 01:16:52.672Z
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. :-)
- FForrester Savell @Forrester_Savell
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)"
}
]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 :-)
- In reply toForrester_Savell⬆:
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! ;-)
- FForrester Savell @Forrester_Savell
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.
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.
- In reply toForrester_Savell⬆:
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.
- FForrester Savell @Forrester_Savell
All this is really helpful, thanks again @Kitch
- 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", ];
- In reply toForrester_Savell⬆:
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)
- FForrester Savell @Forrester_Savell
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.
- FForrester Savell @Forrester_Savell
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;
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. :-)
- FForrester Savell @Forrester_Savell
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?
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. :-)
- FForrester Savell @Forrester_Savell
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
- In reply toKitch⬆:FForrester Savell @Forrester_Savell
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();
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! ✨✨