Set Input and Output Sound Devices in MacOS Ventura

Set Sound Ventura.kmmacros (4.5 KB)

(* Author:	Ike Nassi (nassi@nassi.com)
			2022-11-02
			Written because 13.0 (Ventura) re-wrote System Preferences broke several earlier control panels
			
			Special shout-out to ccstone, whose macro
				Window Analysis Tool for System Events v1.50
			was essential in figuring out what new the UI hierarchy looks like
*)

tell application "Keyboard Maestro Engine"
	set myInput to getvariable "InputDevice"
	set myOutput to getvariable "OutputDevice"
end tell

(*
-- temporary test harness:
set myOutput to "USB Sound Device"
set myInput to "Internal Microphone"
*)
tell application "System Settings"
	
	activate
	delay 1.0
	
	tell application "System Events"
		tell process "System Settings"
			set theWindow to first window
			delay 0.5
		end tell
		
		-- select row mySoundIndex of outline 1 of scroll area 1 of group 1 of splitter group 1 of group 1 of theWindow
		-- use this method since the list of panes can change from machine to machine, but the name does not
		-- Is there a better way to do this?
		keystroke "Sound"
		delay 1.5
		
		tell application process "System Settings"
			tell radio button 1 of tab group 1 of group 2 of scroll area 1 of group 1 of group 2 of splitter group 1 of group 1 of window "Sound"
				click
			end tell
			delay 1
			
			--
			-- Now select the output device
			--
			set theRows to (every row of table 1 of scroll area 1 of group 2 of scroll area 1 of group 1 of group 2 of splitter group 1 of group 1 of window "Sound")
			repeat with aRow in theRows
				-- display dialog "Try this one " & (name of first item of static text of group 1 of UI element 1 of aRow)
				try
					-- why are there multiple items in this group?  I dont' know
					--
					if (name of first item of static text of group 1 of UI element 1 of aRow) starts with myOutput then
						set selected of aRow to true
						-- display dialog "Output set to " & myOutput
						exit repeat
					end if
				on error
					display dialog "Error setting output sound"
				end try
			end repeat
			
			--
			-- Next, Select the input device
			--
			
			tell radio button 2 of tab group 1 of group 2 of scroll area 1 of group 1 of group 2 of splitter group 1 of group 1 of window "Sound"
				click
			end tell
			delay 1
			
			-- Note: the input pane is different from the output pane, so we use "group 2" now instead of "group 1" used in the output pane
			--
			set theRows to (every row of table 1 of scroll area 1 of group 2 of scroll area 1 of group 1 of group 2 of splitter group 1 of group 1 of window "Sound")
			repeat with aRow in theRows
				-- display dialog "Try this one " & (name of first item of static text of group 1 of UI element 1 of aRow)
				try
					if (name of first item of static text of group 1 of UI element 1 of aRow) starts with myInput then
						set selected of aRow to true
						-- display dialog "Input  set to " & myInput
						exit repeat
					end if
				on error
					display dialog "Error setting input sound"
				end try
			end repeat
			
		end tell
	end tell
	
	quit
	
end tell

1 Like

Hey Ike, for macros that use AppleScript actions, the bulk of the script ends up hidden in the screenshot.

To help us better understand what the macro (and by extension, AppleScript does), you can paste the AppleScript itself into the post, surround by three tick (`) marks (like I did with your AppleScript). This will automatically format it for easy legibility.

After that, you can also “hide details” to reduce the visible length of the post if you‘d like. :wink:

On another note... have you ever heard of the Homebrew package switchaudio-osx? I use it to change audio input and output and it’s phenomenal. It’s reported to work on Ventura with Apple Silicon devices; I’m not certain about Intel devices since on the ones I have I am still running Monterey.

-Chris

AppleScript (click to expand/collapse)
(* Author:	Ike Nassi (nassi@nassi.com)
			2022-11-02
			Written because 13.0 (Ventura) re-wrote System Preferences broke several earlier control panels
			
			Special shout-out to ccstone, whose macro
				Window Analysis Tool for System Events v1.50
			was essential in figuring out what new the UI hierarchy looks like
*)
			
tell application "Keyboard Maestro Engine"
	set myInput to getvariable "InputDevice"
	set myOutput to getvariable "OutputDevice"
end tell

(*
-- temporary test harness:
set myOutput to "USB Sound Device"
set myInput to "Internal Microphone"
*)
tell application "System Settings"
	
	activate
	delay 0.5
	
	tell application "System Events"
		tell process "System Settings"
			set theWindow to first window
			delay 0.5
		end tell
		
		-- select row mySoundIndex of outline 1 of scroll area 1 of group 1 of splitter group 1 of group 1 of theWindow
		-- use this method since the list of panes can change from machine to machine, but the name does not
		-- Is there a better way to do this?
		keystroke "Sound"
		delay 1.5
		
		tell application process "System Settings"
			tell radio button 1 of tab group 1 of group 2 of scroll area 1 of group 1 of group 2 of splitter group 1 of group 1 of window "Sound"
				click
			end tell
			delay 1
			
			--
			-- Now select the output device
			--
			set theRows to (every row of table 1 of scroll area 1 of group 2 of scroll area 1 of group 1 of group 2 of splitter group 1 of group 1 of window "Sound")
			repeat with aRow in theRows
				-- display dialog "Try this one " & (name of first item of static text of group 1 of UI element 1 of aRow)
				try
					-- why are there multiple items in this group?  I dont' know
					--
					if (name of first item of static text of group 1 of UI element 1 of aRow) starts with myOutput then
						set selected of aRow to true
						-- display dialog "Output set to " & myOutput
						exit repeat
					end if
				on error
					display dialog "Error setting output sound"
				end try
			end repeat
			
			--
			-- Next, Select the input device
			--
			
			tell radio button 2 of tab group 1 of group 2 of scroll area 1 of group 1 of group 2 of splitter group 1 of group 1 of window "Sound"
				click
			end tell
			delay 1
			
			-- Note: the input pane is different from the output pane, so we use "group 2" now instead of "group 1" used in the output pane
			--
			set theRows to (every row of table 1 of scroll area 1 of group 2 of scroll area 1 of group 1 of group 2 of splitter group 1 of group 1 of window "Sound")
			repeat with aRow in theRows
				-- display dialog "Try this one " & (name of first item of static text of group 1 of UI element 1 of aRow)
				try
					if (name of first item of static text of group 1 of UI element 1 of aRow) starts with myInput then
						set selected of aRow to true
						-- display dialog "Input  set to " & myInput
						exit repeat
					end if
				on error
					display dialog "Error setting input sound"
				end try
			end repeat
			
		end tell
	end tell
	
	quit
	
end tell
1 Like

No, I hadn't heard of switchautdio-osx. I'll check it out. Thanks.

1 Like

Thanks. It's nice. But who knew the name of the program was actually "SwitchAudioSource" ???

Haha yea it baffles me why it’s advertised one way but the package itself has a different name. Makes it impossible for me to remember which is which.

What do you see that's wrong with this?

Execute Shell Script:

SwitchAudioSource -a
echo ""
SwitchAudioSource -t input   -s "Built-in Microphone"
SwitchAudioSource -t output -s "USB Sound Device"
SwitchAudioSource -c

It gives this output:

Ike’s iPhone Microphone
Built-in Microphone
Microsoft Teams Audio
System Audio Recorder
Built-in Output
USB Sound Device        
Microsoft Teams Audio

input audio device set to "Built-in Microphone"
Could not find an audio device named "USB Sound Device" of type output.  Nothing was changed.
Built-in Output

Figured it out. the Actual device name has a number of trailing invisible blank characters!!!

2 Likes

Woah that’s odd, but I’m glad you got it figured out!

For several years I used to use AppleScript to change audio input and output until I discovered the Homebrew package. It’s so fast, reliable and 100% in the background that I’ve never gone back.

The one thing it doesn’t do is connect Bluetooth devices. For instance, AirPods. It can switch to them if they’re already connected to the Mac, but it can’t connect to them otherwise. But I don’t use Bluetooth headphones/speakers enough with my Macs to worry about it.

I agree. Pretty slick. I wrapped its use in a KM macro, since I’ve got multiple Macs, multiple locations, with multiple sound systems. I use the same KM macro to manage the complicated setup. I haven’t tried bluetooth on this, but I use airplay extensively at home, so it will be interested to try it out on that.

I use this AppleScript to connect to my AirPods Max, and it has been very reliable:

use framework "IOBluetooth"
use scripting additions
set AirPodsName to "Max"
on getFirstMatchingDevice(deviceName)
	repeat with device in (current application's IOBluetoothDevice's pairedDevices() as list)
		if (device's nameOrAddress as string) contains deviceName then return device
	end repeat
end getFirstMatchingDevice
on toggleDevice(device)
	if not (device's isConnected as boolean) then
		device's openConnection()
		return "Connecting " & (device's nameOrAddress as string)
	else
		device's closeConnection()
		return "Disconnecting " & (device's nameOrAddress as string)
	end if
end toggleDevice
return toggleDevice(getFirstMatchingDevice(AirPodsName))

Hello Jim (@Jim) and of course all others

first of all, thank you Jim for providing this very handy little AppleScript for toggling the connection to a Bluetooth Device.

for all others:

There is something I've read somewhere in the WWW (World Wide Web) out there when it comes to scripting with the handling of Information in a process of considering using ASObjC. Maybe I am not completely right with this but who knows - I am not perfect and still learning a lot each day - taking your Script @Jim as an example here is a good one to use with this new Information (if it wasn't known before).

Your Script is written using device's nameOrAddress as string. The less knowledge I have is that this will not compile because the Information you are asking for is in the handling of constants (device's nameOrAddress....) and not a class where this eventually might be possible to ask for a "string". In this case you will have to use device's nameOrAddress as text - otherwise you'll get the error code 2741 when debugging the code with Script Debugger.

Maybe Apple has changed something significant to this in AppleScript but all I am aware of is that they didn't.

I've revised the code and migrated it to use properties for a little possible performance boost and readability.


--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
property ptyScriptName : "Toggle Connection to AirPods Max"
property ptyScriptAuthor : "Jim Krenz (KMF=> @Jim)" -- lifting/mod by T.Zittlau (KMF=> @Nr.5-need_input)
property ptyScriptDate : "unknown"
property ptyScriptModDate : "November, 13th 2022 -- GMT+1"
property ptyScriptVer : "1.1"

property ptyExternalLibsUsed : "None"
property ptyOsaxenUsed : "None"

property ptyScriptLang : "AppleScriptObjective-C"
property ptyScriptMigToProp : "YES"
property ptyFrameworksInUse : "IOBluetooth"

property ptyUsedOS : "macOS High Sierra (10.13.6)"
property ptyUsedMac : "MacBook Pro 7.1 13inch 2010"
property ptyUsedAppl : "Script Debugger by LateNight Software Ltd. -- Ver 7.0.13 (7A125)"
--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "IOBluetooth"
use scripting additions

(*
NOTE:

Script was changed due to a compile Error in getting the "IOBluetoothDevice's nameOrAddress"
where "as string" couldn't get compiled because of handling with constants.

Script was also migrated to use properties for a little possible performance boost and better
readability.  
*)

--  classes, constants, and enums used
property IOBluetoothDevice : a reference to current application's IOBluetoothDevice


set AirPodsName to "Max"

on getFirstMatchingDevice(deviceName)
	repeat with device in (IOBluetoothDevice's pairedDevices() as list)
		if (device's nameOrAddress as text) contains deviceName then return device
	end repeat
end getFirstMatchingDevice

on toggleDevice(device)
	if not (device's isConnected as boolean) then
		device's openConnection()
		return "Connecting " & (device's nameOrAddress as text)
	else
		device's closeConnection()
		return "Disconnecting " & (device's nameOrAddress as text)
	end if
end toggleDevice

return toggleDevice(getFirstMatchingDevice(AirPodsName))

Greetings from Germany

Tobias

1 Like

@Nr.5-need_input — nice! Thank you!

You're welcome, Jim (@Jim)

It's always a pleasure for me if I can help others, too when I contribute here.

Greetings from Germany

Tobias

1 Like

Hello Ike (@ikenassi)

thank you for providing the Script to set the Sound in macOS Ventura (13.0+).

Saved it for later in my Snippets to use when upgrading to Ventura.

great Job

Greetings from Germany

Tobias