Logic Pro X- UI element inconsistently labelled
So a lot of my macros, due to working in Logic Pro and it having a uniquely coded UI, use the 'Mouse Click relative to UI element'.
This one I have set up to click where Logic assigns a track output in the inspector and have it type in which Buss I want it to go to:

The code being:
sf.ui.app('com.apple.logic10').mainWindow.groups.whoseDescription.is('Inspector').first.children.whoseRole.is("AXList").first.groups.allItems[3].children.whoseRole.is("AXLayoutArea").first.children.whoseRole.is("AXLayoutItem").first.groups.first.checkBoxes.first.mouseClickElement({
relativePosition: {"x":29,"y":-49},
anchor: "MidCenter",
isRightClick: true,
});
sf.keyboard.press({
keys: "b, right, 3, right, b, u, s, space, 6, 3, return",
});
(Because there aren't many pickable UI elements down in that area of the Logic inspector i'm using my basis for the XY coordinates as the automation bypass button).
Depending on which way the wind blows, sometimes Logic will no longer accept a parameter in the UI element location chain, namely where it says '4. Group'. You can see it in the screengrab. Sometimes, Logic wants to call it '3rd Group' as per here, when I pick the UI element again:

So the macro barely ever works. In the code it looks like it's the difference between a command in there being
first.groups.allItems[3]
and
first.groups.allItems[2]
On any given day Logic will reject that part of the code
Seems like the fix would be to tell it to proceed if the first.groups.allitems value is 2 or 3? Then that would cover whatever Logic feels like labelling it on that day.
Question is... how do I do that?
Thanks!
- Christian Scheuer @chrscheuer2020-12-08 19:18:15.786Z
Hi Dan,
Yea, this is a drag when using UI automation with Logic.
I think the best possible way to fix this is that SoundFlow would support the same type of dynamic mappings of complex UI to simple UI as we do with Pro Tools.
Right now that requires a lot of changes to the core code of SoundFlow though, which makes it hard to push out updates to on a fast basis.
We have a plan for how to do this, but it's a long term plan.Here and now, I would probably address it by looking at the specific script to see how it could be optimized (it wouldn't be possible to solve it just in the macro editor).
- DDan Radclyffe @Dan_Radclyffe
Thanks... any ideas how to make both of these into one command?
first.groups.allItems[2]
first.groups.allItems[3]
Like... first.groups.allItems[=2 or 3]
:)
Jesper Ankarfeldt @JesperA2020-12-09 02:17:27.310Z2020-12-09 05:51:17.278Z
Hi Dan.
So yeah, Logic automation right now isn't the best. It's nice having access to the UI elements, but doesn't help much when Logic keeps changing them :)
So a couple of things, I don't know if you know, but you can also change the UI element names in the UI picker by clicking on an element, and maybe that way make sure it leads the right way:
You could then check wether one or the other is existing. But this would happen in a script and not as a macro though.
Another thing to note is that the output popup is accessible as well. So you can actually choose them directly with soundflow with poupMenuSelect.
Unfortunately the bus names changes when you start assigning things to them.Maybe try the code below and see if it works for you:
var app = sf.ui.app('com.apple.logic10') var inspector = app.mainWindow.groups.whoseDescription.is('Inspector').first var byPassElement = inspector.children.whoseRole.is("AXList").first.children.whoseRole.is("AXList").first.groups.first.groups.whoseTitle.is('Read').first.checkBoxes.whoseDescription.is('bypass').first var outputPath = 'Bus 3' byPassElement.popupMenuSelect({ relativePosition: { x: 38, y: -38 }, isRightClick: true, menuSelector: item => item.filter(i => i.path.slice(-1).join().split(' → ')[0] === outputPath)[0] })
Another pro tip: You can copy your own specific UI element path by using SF's "copy as javascript" on the 3 dots on the top right on the macro, to match your logic window setup, as I think mine gives me a different result.
Let me know if you need more help!- DDan Radclyffe @Dan_Radclyffe
Thank you. Yes i'm aware of the arrows in the UI location chain/macro editor. I'd be lost without them!
In all my tests i've never been able to get SF to directly access a popup menu in Logic's UI. Hence the convoluted XY plot against the automation bypass button!
- DDan Radclyffe @Dan_Radclyffe
@JesperA yep SF is flagging up line 6 of the code where the XY coordinates are. What is the relative position referring to? As in, what's the anchor for the XY coordinates 38 and -38?
Jesper Ankarfeldt @JesperA2020-12-09 16:54:28.301Z
it just seemed more in the middle of the output button as the UI takes from the bypass button top left corner.
So equivalent to you x:29, y: -49. which I think didn't work for me.No matter what window configuration I select, I can't make my codeline not work.
Have you figured out when it changes on your end and doesn't work?- DDan Radclyffe @Dan_Radclyffe
Weird, I can't get your codeline to do anything now. No error comes up, just nothing happens
- In reply toDan_Radclyffe⬆:Kitch Membery @Kitch2020-12-09 02:45:27.419Z2020-12-09 04:18:19.588Z
Hi @Dan_Radclyffe,
Out of interest, does this script work for you for assigning outputs?
const outputPath = 'Bus 1'; 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[2].children.whoseRole.is("AXLayoutArea").whoseDescription.is('Mixer').first; logic.appActivateMainWindow(); mixer.children.whoseRole.is("AXLayoutItem").first.buttons.allItems[4].popupMenuSelect({ menuSelector:item=>item.filter(i=>i.path.slice(-1).join().split(' → ')[0] === outputPath)[0], });
Let me know :-)
Kitch Membery @Kitch2020-12-09 10:41:12.721Z
On further inspection, I see what you mean, as soon as you assign the track to a group the button and group positions change. As @JesperA mentioned some of the UI elements have labels, which is great however if they don't, you are stuck with using the number reference.
In that situation, you could use a try, catch statement like this.
const outputPath = 'Bus 1'; const logic = sf.ui.app('com.apple.logic10'); const inspector = logic.mainWindow.groups.whoseDescription.is('Inspector').first; logic.appActivateMainWindow(); try { let mixer = inspector.children.whoseRole.is("AXList").first.groups.allItems[2].children.whoseRole.is("AXLayoutArea").whoseDescription.is('Mixer').first; mixer.children.whoseRole.is("AXLayoutItem").first.buttons.allItems[4].popupMenuSelect({ menuSelector: item => item.filter(i => i.path.slice(-1).join().split(' → ')[0] === outputPath)[0], }); } catch (err) { let mixer = inspector.children.whoseRole.is("AXList").first.groups.allItems[3].children.whoseRole.is("AXLayoutArea").whoseDescription.is('Mixer').first; mixer.children.whoseRole.is("AXLayoutItem").first.buttons.allItems[4].popupMenuSelect({ menuSelector: item => item.filter(i => i.path.slice(-1).join().split(' → ')[0] === outputPath)[0], }); }
However... again if the Logic Pro X UI changes or the track type is different, the script may not work.
There may be a better way to do this but I have not had that lightbulb moment yet :-)
- DDan Radclyffe @Dan_Radclyffe
Thanks @Kitch. That sounds like it could work, the catch thing! I'll try it at the studio tomorrow.
Jesper Ankarfeldt @JesperA2020-12-09 15:35:46.298Z
Yes, that was also my next go to thing, to look through the different groups, but wanted to make sure we also first covered the option of the different layers that SF has in the macro editor selector :D
- In reply toDan_Radclyffe⬆:DDan Radclyffe @Dan_Radclyffe
@Kitch this try/catch one didn't work for me unfortunately- SF flags line 14, saying 'open from element requires an element'...
Kitch Membery @Kitch2020-12-09 21:46:22.880Z
Sorry about that Dan... With all the UI interface variations, mapping it is an up hill battle.
Try this one it should work for Audio Tracks, I think I tested the previous script with an Instrument track. (I should have specified that).
const outputPath = 'Bus 1'; const logic = sf.ui.app('com.apple.logic10'); const inspector = logic.mainWindow.groups.whoseDescription.is('Inspector').first; logic.appActivateMainWindow(); //Set the Output Path try { let mixer = inspector.children.whoseRole.is("AXList").first.groups.allItems[2].children.whoseRole.is("AXLayoutArea").whoseDescription.is('Mixer').first; mixer.children.whoseRole.is("AXLayoutItem").first.buttons.allItems[6].popupMenuSelect({ menuSelector: item => item.filter(i => i.path.slice(-1).join().split(' → ')[0] === outputPath)[0], }); } catch (err) { let mixer = inspector.children.whoseRole.is("AXList").first.groups.allItems[3].children.whoseRole.is("AXLayoutArea").whoseDescription.is('Mixer').first; mixer.children.whoseRole.is("AXLayoutItem").first.buttons.allItems[6].popupMenuSelect({ menuSelector: item => item.filter(i => i.path.slice(-1).join().split(' → ')[0] === outputPath)[0], }); }
@JesperA from your javascript knowledge what is the best way of doing multiple try catch statements. Maybe by using a switch case and reading the success of the menu selection?
Jesper Ankarfeldt @JesperA2020-12-09 22:52:00.041Z
If it's on the case of 2-3 different outcomes, I've usually just coded it exactly like you've done about.
I would say that my error handling could still get better as well.Kitch Membery @Kitch2020-12-09 22:54:02.416Z
Thanks @JesperA ... Maybe nesting the try catch statements would work best.
- In reply toDan_Radclyffe⬆:
Kitch Membery @Kitch2020-12-30 10:32:43.320Z
Hi @Dan_Radclyffe,
I had a thought earlier today on how to get this working.
Try this code and let me know if it works for you :-)
const outputPath = 'Bus 1'; const logic = sf.ui.app('com.apple.logic10'); const inspector = logic.mainWindow.groups.whoseDescription.is('Inspector').first; logic.appActivateMainWindow(); //Set the Output Path let mixer = inspector.children.whoseRole.is("AXList").first.groups .filter(g => g.children.whoseRole.is("AXLayoutArea").whoseDescription.is('Mixer').first.exists) .map(g => g.children.whoseRole.is("AXLayoutArea").whoseDescription.is('Mixer').first)[0]; mixer.children.whoseRole.is("AXLayoutItem").first.buttons.allItems[6].popupMenuSelect({ menuSelector: item => item.filter(i => i.path.slice(-1).join().split(' → ')[0] === outputPath)[0], });
- DDan Radclyffe @Dan_Radclyffe
Only just managed to try this out- getting this error:
"No Menu Item was returned by menuSelector callback"
I have a bunch of channel strip components (preamp controls, eq, compressor) hidden. Presumably that's something to do with it?
- In reply toKitch⬆:AAlex Oldroyd @Alex_Oldroyd8
Hey @Kitch
This code is amazing. I've had real difficulty setting output routing.
First - is there way to introduce wildcards into the menu selection? As when you set up an auxiliary on a chosen bus, the bus name changes to include the aux name.
Second - have you found a better way than nesting try/catch error or if/else arguments? This is where i've got to to open plugin on first plugin slot no matter how many plugins slots are active... also I can't seem to make the button number the variable, hence writing out entire chain...
// @ts-nocheck const btnPath1 = sf.ui.app("com.apple.logic10").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.groups.allItems[1].buttons.whoseDescription.is("list").first const btnPath2 = sf.ui.app("com.apple.logic10").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.groups.allItems[2].buttons.whoseDescription.is("list").first const btnPath3 = sf.ui.app("com.apple.logic10").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.groups.allItems[3].buttons.whoseDescription.is("list").first const btnPath4 = sf.ui.app("com.apple.logic10").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.groups.allItems[4].buttons.whoseDescription.is("list").first const btnPath5 = sf.ui.app("com.apple.logic10").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.groups.allItems[5].buttons.whoseDescription.is("list").first const btnPath6 = sf.ui.app("com.apple.logic10").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.groups.allItems[6].buttons.whoseDescription.is("list").first const btnPath7 = sf.ui.app("com.apple.logic10").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.groups.allItems[7].buttons.whoseDescription.is("list").first const btnPath8 = sf.ui.app("com.apple.logic10").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.groups.allItems[8].buttons.whoseDescription.is("list").first const btnPath9 = sf.ui.app("com.apple.logic10").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.groups.allItems[9].buttons.whoseDescription.is("list").first const btnPath10 = sf.ui.app("com.apple.logic10").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.groups.allItems[10].buttons.whoseDescription.is("list").first const btnPath11 = sf.ui.app("com.apple.logic10").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.groups.allItems[11].buttons.whoseDescription.is("list").first const btnPath12 = sf.ui.app("com.apple.logic10").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.groups.allItems[12].buttons.whoseDescription.is("list").first const btnPath13 = sf.ui.app("com.apple.logic10").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.groups.allItems[13].buttons.whoseDescription.is("list").first const btnPath14 = sf.ui.app("com.apple.logic10").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.groups.allItems[14].buttons.whoseDescription.is("list").first const pluginPath = ['Utility', 'Gain', 'Mono'] if (btnPath13.exists) { btnPath13.popupMenuSelect({ menuPath: pluginPath }); } else { if (btnPath12.exists) { btnPath12.popupMenuSelect({ menuPath: pluginPath }); } else { if (btnPath11.exists) { btnPath11.popupMenuSelect({ menuPath: pluginPath }); } else { if (btnPath10.exists) { btnPath10.popupMenuSelect({ menuPath: pluginPath }); } else { if (btnPath9.exists) { btnPath9.popupMenuSelect({ menuPath: pluginPath }); } else { if (btnPath8.exists) { btnPath8.popupMenuSelect({ menuPath: pluginPath }); } else { if (btnPath7.exists) { btnPath7.popupMenuSelect({ menuPath: pluginPath }); } else { if (btnPath6.exists) { btnPath6.popupMenuSelect({ menuPath: pluginPath }); } else { if (btnPath5.exists) { btnPath5.popupMenuSelect({ menuPath: pluginPath }); } else { if (btnPath4.exists) { btnPath4.popupMenuSelect({ menuPath: pluginPath }); } else { if (btnPath3.exists) { btnPath3.popupMenuSelect({ menuPath: pluginPath }); } else { if (btnPath2.exists) { btnPath2.popupMenuSelect({ menuPath: pluginPath }); } else { if (btnPath1.exists) { btnPath1.popupMenuSelect({ menuPath: pluginPath }); } } } } } } } } } } } } }
Kitch Membery @Kitch2022-01-26 23:35:58.111Z
Hi @Alex_Oldroyd8,
To address your first question "is there a way to introduce wildcards into the menu selection? As when you set up an auxiliary on a chosen bus, the bus name changes to include the aux name."
This is where Logics UI elements changing is problematic when trying to address it with UI automation. It will take a bit of investigation to work out the best way to pinpoint which button is which. This is the main reason we eventually want to have an API set up similar to what we have with Pro Tools so the user could address it with something along the lines of
sf.ui.proTools.selectedTrack.trackOutputSelect()
with deciphering working behind the scenesAs far as the Try Catch blocks go. Instead of using try-catch blocks for these scenarios, A better approach would be to work out what has changed visually in the GUI and then use a switch statement to determine the change in the element's index.
When I have time, I will investigate it further. :-)
- AAlex Oldroyd @Alex_Oldroyd8
Thanks Kitch. From what I can tell, the only difference to the bus name change is that it adds the name of the aux after, so if wildcards could be implemented, it would solve the issue.
Kitch Membery @Kitch2022-01-26 23:40:32.261Z
Ahh OK. I'll check it out. Is there a possibility it could be called anything else?
- AAlex Oldroyd @Alex_Oldroyd8
No I think it will always be Bus # → 'Aux name' - shown below
before bus is used
once bus is used
fyi sometimes logic seems to use double spaces in bus names. eg if you use i/o labels, it will be Bus 22 *space *space (i/o label)
Kitch Membery @Kitch2022-01-27 01:01:06.263Z
Making some progress, but let me first ask. What is the workflow you would like to achieve with this specific menu?
Is it just for setting the bus output?
And say if you were to make a command template from the script, what would the template properties be?
ie
Bus Output Slot: (1-12) or next free slot?
Bus Menu PathRock on!
- AAlex Oldroyd @Alex_Oldroyd8
I want to send to 'Bus 1' whether it's already in use or not. If Bus 1 isn't currently active, I want to set it up, if Bus 1 is already in use for an aux, I want to send to it. I use i/o labels so i use the same bus numbers for specific things. ie Bus 74. (Drum Buss)
My goal is to incorporate this into a longer script. I'd like to select all tracks with names containing either drum, kick, snare, snr, hat, hihat et. etc; colour them red; give them all drum icon; send them to "Bus 74 (Drum Buss)"; create track stack (Bus 74); colour aux track red; give icon; open patch preset...
Another scenario is:
Create send to Bus 1 (LV Rvb 1); open channel strip preset on aux; name aux LV Rvb 1;I imagine that for a lot of this the ability to use wildcards in text and popup menu items selection might be necessary...
🤘
Kitch Membery @Kitch2022-01-27 01:24:24.313Z
I love the end goal, but if we could focus on the bus/send assignment script that would be great.
From your post though I can see you want to set the send for the a selected track.
Are you just trying to add a "new" send to the track or replace the first send or one of the 12 maximum send slots.
Rock on :-)
- AAlex Oldroyd @Alex_Oldroyd8
Either add a new send if empty or add new send to first send slot.
Updated my response above. hopefully it's a little clearer
- In reply toKitch⬆:AAlex Oldroyd @Alex_Oldroyd8
Oh and the template property would be the bus number to send to. Either for output or send.
- In reply toAlex_Oldroyd8⬆:AAlex Oldroyd @Alex_Oldroyd8
I will investigate switch statements. Cheers
Kitch Membery @Kitch2022-01-26 23:44:08.441Z
If it's just the Bus name changing It may not need any switch statements.
I'll grab my laptop and see if I can work it out. :-)
- AAlex Oldroyd @Alex_Oldroyd8
Hero!!
- In reply toKitch⬆:AAlex Oldroyd @Alex_Oldroyd8
Hey @kitch. Hope you’re well. Just wondering if you’ve made any progress with implementing wildcards or won’t output bud name changes… :)
Kitch Membery @Kitch2022-02-07 23:05:39.521Z
Have not forgotten about this one @Alex_Oldroyd8...
I have made considerable inroads into it but there are quite a lot of changes to the GUI depending on the track type.I have it working on instrument tracks but need get it working on the other track types (ie Audio tracks, Drummer tracks etc).
Here is it in action on an Instrument track;
I'll keep you posted on my progress, but It might be a week or so. :-)
- AAlex Oldroyd @Alex_Oldroyd8
It's looking good, Kitch!! Excited!
- JIn reply toDan_Radclyffe⬆:Johan Nordin @Johan_Nordin
Hello! Just wanted to check if you have managed to create a script that is working both for audio tracks and instrument tracks.
Johan
- AIn reply toDan_Radclyffe⬆:Alex Oldroyd @Alex_Oldroyd8
Has anyone managed to get this to work?
- RRyan Hayes @Ryan_Hayes
I almost have one working as a Switch Case Using Open select Pop up menu. Ran into the same problem when switching sessions. Trying to code that in next. Right now I have it as
shift - to open plugins on Stereo Out or Aux on Inspector (mono or stereo)
No modifyer keys - opens plugins native to the channel (mono or stereo)combine these with shift and it moves it to stereo out or AUX
cmd - to open dual mono
option - im using to open lwo dsp versions of the plugin if it has it.
ctrl - if i could get it to work.. would be mono-stereo version- AAlex Oldroyd @Alex_Oldroyd8
Ooh sounds good!! Do keep us updated!