Due to some bug somewhere, my speaker balance kept moving off-center and it was getting annoying. So I cobbled together the code below (based on this question) which—to my surprise—does compile and run on macOS 11.1 / Swift 5.3.2.
I am a beginner and am trying to learn, trying to avoid errors and adhere to the DRY principle. I would also like to know if there are any glaring mistakes.
For example, I believe there is a way to make a single function that can return the default Audio Device as well as fetch properties from it, since there are a lot of similarities between getDefaultOutputDevice()
and aPropGet()
I also am not at all sure about the use of the struct {...}
to declare "global" variables.
Can anyone please pass along any pointers?
To compile save the code below as bal-reset.swift
and build with swiftc -O bal_reset.swift
import AudioToolbox
import CoreAudio
import Foundation
// global variables
struct g {
static var deviceId: AudioDeviceID = 0
static var deviceName = "" as CFString
static var eq_balance: Float32 = 0.50 // 0.0 (left) through 1.0 (right)
}
func getDefaultOutputDevice() {
var aSize = UInt32(MemoryLayout.size(ofValue: g.deviceId))
var address = AudioObjectPropertyAddress(
mSelector: kAudioHardwarePropertyDefaultOutputDevice,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster)
var err = AudioObjectGetPropertyData(
AudioObjectID(kAudioObjectSystemObject),
&address,
0,
nil,
&aSize,
&g.deviceId)
if (err == 0) {
address.mSelector = kAudioDevicePropertyDeviceNameCFString
aSize = UInt32(MemoryLayout.size(ofValue: g.deviceName))
err = AudioObjectGetPropertyData(
g.deviceId,
&address,
0,
nil,
&aSize,
&g.deviceName)
if (err == 0) {
print("dev:", String(g.deviceName) + " [" + String(g.deviceId) + "]")
} else {
print("dev:", g.deviceId)
}
} else {
print("error [" + String(err) + "] could not determine output device")
exit(1)
}
}
getDefaultOutputDevice()
func aPropGet(selector: UInt32) -> (err: OSStatus, val: Float32) {
var val: Float32 = 0.0
var aSize = UInt32(MemoryLayout.size(ofValue: val))
var address = AudioObjectPropertyAddress(
mSelector: selector,
mScope: kAudioObjectPropertyScopeOutput,
mElement: kAudioObjectPropertyElementMaster
)
let err = AudioObjectGetPropertyData(
g.deviceId,
&address,
0,
nil,
&aSize,
&val)
return (err, val)
}
func setBalance(selector: UInt32) -> OSStatus {
let aSize = UInt32(MemoryLayout.size(ofValue: g.eq_balance))
var address = AudioObjectPropertyAddress(
mSelector: selector,
mScope: kAudioObjectPropertyScopeOutput,
mElement: kAudioObjectPropertyElementMaster
)
let err = AudioObjectSetPropertyData(
g.deviceId,
&address,
0,
nil,
aSize,
&g.eq_balance)
return err
}
func getBalance() -> (err: OSStatus, val: Float32) {
var res = aPropGet(selector: kAudioDevicePropertyStereoPan)
if (res.err != 0) {
res = aPropGet(selector: kAudioHardwareServiceDeviceProperty_VirtualMasterBalance)
}
return (res.err, res.val)
}
var res = aPropGet(selector: kAudioHardwareServiceDeviceProperty_VirtualMasterVolume)
if (res.err == 0) {
print("vol:", String(format: "%.0f", res.val*100))
}
res = getBalance()
switch res.err {
case 0:
print("old:", String(format: "%.2f", res.val))
case kAudioCodecUnknownPropertyError:
print("device does not support balance adjustment")
exit(0)
default:
print("unknown error [" + String(res.err) + "] while trying to query balance")
exit(1)
}
switch res.val {
case 0.5:
exit(0)
default:
var bal = setBalance(selector: kAudioDevicePropertyStereoPan)
if (bal != 0) {
bal = setBalance(selector: kAudioHardwareServiceDeviceProperty_VirtualMasterBalance)
}
switch bal {
case 0:
print("new:", String(format: "%.2f", g.eq_balance))
exit(0)
default:
print("error [" + String(bal) + "] while trying to set balance")
exit(1)
}
}