Title
Logic Pro X Bouncing selected tracks as Stems
What do you expect to happen when you run the script/macro?
Script should read which tracks are selected and apply script to each. 1. ensure track is solo enabled 2. open bounce track dialogue and select all options as desired 3. open desired bounce folder 4. name bounce file name of track with pre or post script as desired 5. bounce and wait for bounce to complete 6. move to next selected track and repeat
Are you seeing an error?
The errors are numerous and seemingly random. Often re-running the script completely solves any issues.
Ideally i'd like to sort out these errors in the first place but additionally - is there a way to use error handling like loop until success when calling a function? ie loop bouncestem() until there is no error? I can only find information on doing that in relation to selecting a popup menu item for example.
Here are some of the most common errors below
05.08.2022 16:25:52.03 [Backend]: !! Command Error: !! BOUNCE ALL STEMS [user:cl1dfytrs0001lz10tvlm49uj:cl1dg31150004lz103udco1eh]:
Error: Object reference not set to an instance of an object.
(!! BOUNCE ALL STEMS line 84)
05.08.2022 16:27:10.63 [Backend]: !! Command Error: !! BOUNCE ALL STEMS [user:cl1dfytrs0001lz10tvlm49uj:cl1dg31150004lz103udco1eh]:
Couldn't locate AxElementArrayIndexedItem (!! BOUNCE ALL STEMS: Line 31)
05.08.2022 16:28:07.10 [Backend]: !! Command Error: !! BOUNCE ALL STEMS [user:cl1dfytrs0001lz10tvlm49uj:cl1dg31150004lz103udco1eh]:
Object reference not set to an instance of an object. (!! BOUNCE ALL STEMS: Line 141)
System.NullReferenceException: Object reference not set to an instance of an object.
at SoundFlow.Shortcuts.Ax.AxNodes.AxElementArrayIndexedItem.GetUIElement() + 0x3e
at SoundFlow.Shortcuts.Ax.AxNodes.AxElement.LoadUIElement() + 0x1b
at SoundFlow.Shortcuts.Ax.AxNodes.AxElement.get_UIElement() + 0x36
at SoundFlow.Shortcuts.Automation.AutoActionResult.RequireUI(AxElement, String) + 0x24
at SoundFlow.Shortcuts.Automation.Actions.ClickButtonAction.d__11.MoveNext() + 0x40
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() + 0x1c
at sfbackend!+0x14f0935
at sfbackend!+0x14f0866
at SoundFlow.Shortcuts.Automation.AutoAction`1.d__20.MoveNext() + 0x2e2
What happens when you run this script?
The script will often work on one or two tracks, then an error will occur and cause the script to fail. I then have to work out how far it has got and re-start from the failed point. Of course an automatic bouncing script like this is only really useful if it will reliably run from start to finish... otherwise i have to monitor it which ruins the entire point!!
Thanks a lot
How were you running this script?
I used a Stream Deck button
How important is this issue to you?
5
Details
{ "inputExpected": "Script should read which tracks are selected and apply script to each. \n1. ensure track is solo enabled\n2. open bounce track dialogue and select all options as desired\n3. open desired bounce folder\n4. name bounce file name of track with pre or post script as desired\n5. bounce and wait for bounce to complete\n6. move to next selected track and repeat", "inputIsError": true, "inputError": "The errors are numerous and seemingly random. Often re-running the script completely solves any issues.\n\nIdeally i'd like to sort out these errors in the first place but additionally - is there a way to use error handling like loop until success when calling a function? ie loop bouncestem() until there is no error? I can only find information on doing that in relation to selecting a popup menu item for example.\n\nHere are some of the most common errors below\n05.08.2022 16:25:52.03 [Backend]: !! Command Error: !! BOUNCE ALL STEMS [user:cl1dfytrs0001lz10tvlm49uj:cl1dg31150004lz103udco1eh]:\nError: Object reference not set to an instance of an object.\n(!! BOUNCE ALL STEMS line 84) \n\n05.08.2022 16:27:10.63 [Backend]: !! Command Error: !! BOUNCE ALL STEMS [user:cl1dfytrs0001lz10tvlm49uj:cl1dg31150004lz103udco1eh]:\nCouldn't locate AxElementArrayIndexedItem (!! BOUNCE ALL STEMS: Line 31)\n\n05.08.2022 16:28:07.10 [Backend]: !! Command Error: !! BOUNCE ALL STEMS [user:cl1dfytrs0001lz10tvlm49uj:cl1dg31150004lz103udco1eh]:\nObject reference not set to an instance of an object. (!! BOUNCE ALL STEMS: Line 141)\n System.NullReferenceException: Object reference not set to an instance of an object.\n at SoundFlow.Shortcuts.Ax.AxNodes.AxElementArrayIndexedItem.GetUIElement() + 0x3e\n at SoundFlow.Shortcuts.Ax.AxNodes.AxElement.LoadUIElement() + 0x1b\n at SoundFlow.Shortcuts.Ax.AxNodes.AxElement.get_UIElement() + 0x36\n at SoundFlow.Shortcuts.Automation.AutoActionResult.RequireUI(AxElement, String) + 0x24\n at SoundFlow.Shortcuts.Automation.Actions.ClickButtonAction.d__11.MoveNext() + 0x40\n--- End of stack trace from previous location where exception was thrown ---\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() + 0x1c\n at sfbackend!+0x14f0935\n at sfbackend!+0x14f0866\n at SoundFlow.Shortcuts.Automation.AutoAction`1.d__20.MoveNext() + 0x2e2", "inputWhatHappens": "The script will often work on one or two tracks, then an error will occur and cause the script to fail. I then have to work out how far it has got and re-start from the failed point. Of course an automatic bouncing script like this is only really useful if it will reliably run from start to finish... otherwise i have to monitor it which ruins the entire point!!\n\nThanks a lot", "inputHowRun": { "key": "-MpfwmPg-2Sb-HxHQAff", "title": "I used a Stream Deck button" }, "inputImportance": 5, "inputTitle": "Logic Pro X Bouncing selected tracks as Stems" }
Source
//ask where you would like stems to be bounced to
const bounceFolder = prompt("Where would you like to bounce");
//preText or postText?
const preText = prompt("PreText?")
const postText = prompt("postText?")
bounceStems()
function bounceStems() {
const app = sf.ui.app('com.apple.logic10')
const logic = sf.ui.app("com.apple.logic10");
logic.appActivateMainWindow()
/**
* @param {object} obj
* @param {'Solo'|'Mute'} obj.buttonName
* @param {'Enable'|'Disable'|'Toggle'} [obj.targetValue]
*/
//make sure solo button is engaged
function ensureButton({ buttonName, targetValue }) {
const logic = sf.ui.app('com.apple.logic10');
const inspector = logic.mainWindow.groups.whoseDescription.is("Inspector").first
const mixer = inspector.children.whoseRole.is("AXList").first.groups.allItems[returnCorrectAllitemsNumber()].children.whoseRole.is("AXLayoutArea").whoseDescription.is("Mixer").first
const soloButtonMixer = mixer.children.whoseRole.is("AXLayoutItem").first.buttons.whoseDescription.is(buttonName).first;
function isTrackSoloButtonEnabled() {
//const soloButtonMixer = sf.ui.app("com.apple.logic10").mainWindow.groups.whoseDescription.is("Inspector").first.children.whoseRole.is("AXList").first.groups.allItems[returnCorrectAllitemsNumber()].children.whoseRole.is("AXLayoutArea").whoseDescription.is("Mixer").first.children.whoseRole.is("AXLayoutItem").first.buttons.whoseDescription.is("solo").first;
return soloButtonMixer.value.invalidate().value
}
const isButtonEngaged = isTrackSoloButtonEnabled() === 'on';
switch (true) {
case targetValue === "Enable" && !isButtonEngaged:
soloButtonMixer.mouseClickElement({
anchor: "MidCenter",
isOption: true,
});
break;
case targetValue === "Disable" && isButtonEngaged:
soloButtonMixer.mouseClickElement({
anchor: "MidCenter",
isOption: true,
});
break;
case targetValue === "Toggle":
soloButtonMixer.mouseClickElement({
anchor: "MidCenter",
isOption: true,
});
break;
}
}
//attempt to check name. If there's an error, it's probably still bouncing so repeat.
function waitForBounceToComplete() {
while (true) {
try {
if (checkTrackName()) {
//We didn't get an error - and the modal popup doesn't exist.
//Break out, the bounce is complete
sf.keyboard.press({ keys: 'escape' })
break;
}
} catch (err) {
//We got an error. Logic is still busy. Continue with another loop
}
//Wait a bit before continuing
sf.wait({ intervalMs: 200 });
}
}
function bounceStem() {
// Get Selected Track and Project Name
const logic = sf.ui.app('com.apple.logic10');
const trackArea = logic.mainWindow.groups.whoseDescription.is('Tracks').first.groups.whoseDescription.is('Tracks');
const trackHeaders = trackArea.allItems[1].splitGroups.first.splitGroups.allItems[1].scrollAreas.first.groups.whoseDescription.is('Tracks header').first;
const selectedTrackName = trackHeaders.getElements('AXSelectedChildren').first.getString("AXDescription").match(/“(.*)”/)[1]
const projectName = logic.mainWindow.getElement("AXTitleUIElement").value.invalidate().value.split(" - ")[0];
const bounceButton = sf.ui.app("com.apple.logic10").windows.first.buttons.whoseTitle.is("Bounce").first
const stemName = `${preText}${selectedTrackName}${postText}`
sf.keyboard.press({ keys: 'escape' })
ensureButton({
buttonName: "Solo",
targetValue: "Enable",
});
app.getMenuItem('File', 'Bounce', 'Project or Section…').elementClick();
app.children.whoseRole.is("AXWindow").first.scrollAreas.first.tables.first.children.whoseRole.is("AXRow").first.children.whoseRole.is("AXCell").first.checkBoxes.first.checkboxSet({
targetValue: "Enable",
onError: "Continue",
});
app.children.whoseRole.is("AXWindow").first.scrollAreas.first.tables.first.children.whoseRole.is("AXRow").first.children.whoseRole.is("AXCell").allItems[1].getElement("AXTitleUIElement").mouseClickElement({
anchor: "MidCenter",
onError: "Continue",
});
app.children.whoseRole.is("AXWindow").first.scrollAreas.first.tables.first.children.whoseRole.is("AXRow").allItems[1].children.whoseRole.is("AXCell").first.checkBoxes.first.checkboxSet({
targetValue: "Disable",
onError: "Continue",
});
app.children.whoseRole.is("AXWindow").first.scrollAreas.first.tables.first.children.whoseRole.is("AXRow").allItems[2].children.whoseRole.is("AXCell").first.checkBoxes.first.checkboxSet({
targetValue: "Disable",
onError: "Continue",
});
app.children.whoseRole.is("AXWindow").first.scrollAreas.first.tables.first.children.whoseRole.is("AXRow").allItems[3].children.whoseRole.is("AXCell").first.checkBoxes.first.checkboxSet({
targetValue: "Disable",
onError: "Continue",
});
app.children.whoseRole.is("AXWindow").first.groups.first.checkBoxes.whoseTitle.is("Add to Project").first.checkboxSet({
targetValue: "Disable",
onError: "Continue",
});
app.children.whoseRole.is("AXWindow").first.groups.first.popupButtons.allItems[1].popupMenuSelect({
menuPath: ["Wave"],
onError: "Continue",
});
sf.ui.app("com.apple.logic10").windows.first.buttons.whoseTitle.is("OK").first.elementClick();
//Wait for the progress window to appear
sf.wait({ intervalMs: 2000 });
searchForBounceFolder(bounceFolder);
// Set the "Save as..." Field
logic.windows.first.textFields.first.elementSetTextAreaValue({
value: stemName
});
sf.wait({ intervalMs: 1000 });
if (bounceButton.exists) {
bounceButton.elementClick();
}
else {
sf.wait({ intervalMs: 2000 });
bounceButton.elementClick();
}
//Wait for the progress window to disappear
waitForBounceToComplete();
sf.wait({ intervalMs: 2000 });
sf.keyboard.press({ keys: 'escape' })
ensureButton({
buttonName: "Solo",
targetValue: "Disable",
});
}
function checkTrackName() {
sf.keyboard.press({ keys: 'escape' })
sf.clipboard.clear();
app.getMenuItem('Track', 'Rename Track').elementClick();
app.getMenuItem('Edit', 'Copy').elementClick();
sf.keyboard.press({ keys: 'escape' })
var trackName = sf.clipboard.waitForText().text;
sf.wait({ intervalMs: 100 })
return trackName
}
function doForEachSelectedTrack(functionToDo) {
const logic = sf.ui.app('com.apple.logic10');
const trackArea = logic.mainWindow.groups.whoseDescription.is('Tracks').first.groups.whoseDescription.is('Tracks');
const trackHeaders = trackArea.allItems[1].splitGroups.first.splitGroups.allItems[1].scrollAreas.first.groups.whoseDescription.is('Tracks header').first;
//Get Selected track names
const selectedTrackNames = trackHeaders.getElements('AXSelectedChildren').map(trackHeader => trackHeader.getString("AXDescription").match(/“(.*)”/)[1]);
selectedTrackNames.forEach(item => functionToDo(item));
}
function selectTrackAndPerformFunction(trackName) {
let originalMousePosition = sf.mouse.getPosition().position;
const logicApp = sf.ui.app("com.apple.logic10");
const trackToSelect = () => {
const trackHeaders = logicApp.invalidate().mainWindow.groups.whoseDescription.is("Tracks").first
.groups.whoseDescription.is("Tracks").allItems[1].splitGroups.first.splitGroups.allItems[1]
.scrollAreas.first.groups.whoseDescription.is("Tracks header").first.children;
const trackToSelect = trackHeaders.find(x => x.textFields.whoseDescription.is(trackName).first.exists);
return trackToSelect
}
if (!trackToSelect()) {
log(`Track with the name '${trackName}' not found`)
throw 0;
}
const mainWindowFrame = logicApp.mainWindow.frame;
const logicRulerFrame = logicApp.mainWindow.groups.whoseDescription.is("Tracks").first.groups.whoseDescription.is("Tracks").allItems[1]
.splitGroups.first.splitGroups.first.scrollAreas.first.frame;
const trackFrame = trackToSelect().frame;
const mainWindowTotalY = (mainWindowFrame.y + mainWindowFrame.h);
const offWindowBottom = ((trackFrame.y + trackFrame.h) > (mainWindowFrame.y + mainWindowFrame.h));
const offScreenTop = (trackFrame.y < logicRulerFrame.y + logicRulerFrame.h);
const trackAreaFrame = sf.ui.app("com.apple.logic10").mainWindow.groups.whoseDescription.is("Tracks").first
.groups.whoseDescription.is("Tracks").allItems[1].splitGroups.first.splitGroups.allItems[1].scrollAreas.allItems[1].frame;
const middleOfTrackHeaderPane = { x: trackAreaFrame.x + (trackAreaFrame.w / 2), y: trackAreaFrame.y + (trackAreaFrame.h / 2) };
sf.mouse.setPosition({ position: middleOfTrackHeaderPane })
if (offWindowBottom) {
sf.mouse.setPosition({ position: middleOfTrackHeaderPane })
sf.mouse.scroll({
delta: -(trackFrame.y + trackFrame.h - mainWindowTotalY),
delta2: 0,
unit: "Pixel"
})
}
else if (offScreenTop) {
sf.mouse.setPosition({ position: middleOfTrackHeaderPane })
sf.mouse.scroll({
delta: ((logicRulerFrame.y + logicRulerFrame.h) - trackFrame.y),
delta2: 0,
unit: "Pixel"
})
}
trackToSelect().mouseClickElement({
relativePosition: { x: (trackFrame.w / 2), y: 5 }
})
sf.mouse.setPosition({ position: originalMousePosition })
/*
try {
bounceStem()
}
catch (error) {
sf.keyboard.press({
keys: "escape",
});
log(trackName);
bounceStem();
}
*/
bounceStem();
}
doForEachSelectedTrack(selectTrackAndPerformFunction);
}
function whatTypeOfTrack() {
const logic = sf.ui.app("com.apple.logic10");
const channel = logic.mainWindow.groups.whoseDescription.is("Inspector").first.children.whoseRole.is("AXList").first.groups.allItems[3].children.whoseRole.is("AXLayoutArea").whoseDescription.is("Mixer").first.children.whoseRole.is("AXLayoutItem")
const inputMonitoring = logic.mainWindow.groups.whoseDescription.is("Inspector").first.children.whoseRole.is("AXList").first.groups.allItems[2].children.whoseRole.is("AXLayoutArea").whoseDescription.is("Mixer").first.children.whoseRole.is("AXLayoutItem").first.buttons.whoseDescription.is("monitoring").first;
const inputMonitoringOther = logic.mainWindow.groups.whoseDescription.is("Inspector").first.children.whoseRole.is("AXList").first.groups.allItems[3].children.whoseRole.is("AXLayoutArea").whoseDescription.is("Mixer").first.children.whoseRole.is("AXLayoutItem").first.buttons.whoseDescription.is("monitoring").first;
const monoStereo = logic.mainWindow.groups.whoseDescription.is("Inspector").first.children.whoseRole.is("AXList").first.groups.allItems[2].children.whoseRole.is("AXLayoutArea").whoseDescription.is("Mixer").first.children.whoseRole.is("AXLayoutItem").first.buttons.whoseDescription.is("channel mode").first;
const monoStereoOther = logic.mainWindow.groups.whoseDescription.is("Inspector").first.children.whoseRole.is("AXList").first.groups.allItems[3].children.whoseRole.is("AXLayoutArea").whoseDescription.is("Mixer").first.children.whoseRole.is("AXLayoutItem").first.buttons.whoseDescription.is("channel mode").first;
//if audio
if ((inputMonitoring.exists) || (inputMonitoringOther.exists)) {
return ('Audio')
}
//if aux
if (((monoStereo.exists) || (monoStereoOther.exists) && (!inputMonitoring.exists))) {
return ('Aux')
}
//if instrument
if (!monoStereo.exists) {
return ('Instrument')
}
}
function returnCorrectAllitemsNumber() {
if (whatTypeOfTrack() === 'Aux') {
return '2'
}
if (whatTypeOfTrack() === 'Audio') {
return '3'
}
if (whatTypeOfTrack() === 'Instrument') {
return '3'
}
}
function searchForBounceFolder(folderPath) {
const logic = sf.ui.app("com.apple.logic10")
const bounceDlg = logic.windows.first;
// Shortcut for opening the "Go To Folder" sheet. (Keyboard simulation is needed here)
sf.keyboard.press({ keys: "cmd+shift+g", });
// Wait for sheet
bounceDlg.sheets.first.elementWaitFor();
// Set Folder Path New
bounceDlg.sheets.first.comboBoxes.first.elementSetTextAreaValue({
value: folderPath,
onError: "Continue",
});
// Close "Go To Folder" sheet
sf.keyboard.press({ keys: "return" });
bounceDlg.sheets.first.elementWaitFor({ waitForNoElement: true });
}
Links
User UID: Kf7km0JU15Q6NnRTk6HrX1PmHaw1
Feedback Key: sffeedback:Kf7km0JU15Q6NnRTk6HrX1PmHaw1:-N8ids8mHzF4HGThHhHF
- Kitch Membery @Kitch2022-08-08 18:34:31.080Z
Hi @Alex_Oldroyd8,
It looks like Christian was able to answer your error handling question in the following thread;
Are any of these errors happening on 100% of each run of the script?
As you mentioned, ideally sorting out these errors in the first place is the best approach. If you are able to narrow down and reproduce these random errors with smaller scripts, it may be worthwhile logging bug reports for them via the help/issue workflow. That way it will be easier for us to investigate further.
I will say that I've noticed I occasionally get a "Object reference not set to an instance of an object." error in Logic, but have neglected to log a report for it. I'll be sure to do so if I can narrow things down when I see the error next.
Rock on!
- AAlex Oldroyd @Alex_Oldroyd8
Great thanks Kitch. I will log the errors next time they come up