This is fairly simple (I think). I'm simply wanting to get a notification in my application whenever a user changes the default sound input or sound output device in System Preferences - Sound. I'll be darned if I'm able to dig it out of the Apple docs, however.
As a side note, this is for OSX, not IOS.
Thanks guys!
Set up an AudioObjectPropertyAddress for the default output device:
AudioObjectPropertyAddress outputDeviceAddress = {
kAudioHardwarePropertyDefaultOutputDevice,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
Then, use AudioObjectAddPropertyListener to register a listener for changes in the default device:
AudioObjectAddPropertyListener(kAudioObjectSystemObject,
&outputDeviceAddress,
&callbackFunction, nil);
The callback function looks like this:
OSStatus callbackFunction(AudioObjectID inObjectID,
UInt32 inNumberAddresses,
const AudioObjectPropertyAddress inAddresses[],
void *inClientData)
As a side note, you should also use AudioObjectPropertyAddress to tell the HAL to manage its own thread for notifications. You do this by setting the run loop selector to NULL. I actually perform this step before setting up the output device listener.
AudioObjectPropertyAddress runLoopAddress = {
kAudioHardwarePropertyRunLoop,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
CFRunLoopRef runLoop = NULL;
UInt32 size = sizeof(CFRunLoopRef);
AudioObjectSetPropertyData(kAudioObjectSystemObject,
&runLoopAddress, 0, NULL, size, &runLoop);
Here's how to do it in Swift:
Register for notifications by adding a listener block, for example when a View Controller loads its view. The addListenerBlock function simplifies adding property listener blocks. Swift allows parameters to be variables, which is very convenient for the forPropertyAddress parameter. When calling addListenerBlock, the property address parameters are just plugged in.
import Cocoa
import CoreAudio
class ViewController: NSViewController {
// Utility function to simplify adding listener blocks:
func addListenerBlock( listenerBlock: AudioObjectPropertyListenerBlock, onAudioObjectID: AudioObjectID, var forPropertyAddress: AudioObjectPropertyAddress) {
if (kAudioHardwareNoError != AudioObjectAddPropertyListenerBlock(onAudioObjectID, &forPropertyAddress, nil, listenerBlock)) {
print("Error calling: AudioObjectAddPropertyListenerBlock") }
}
override func viewDidLoad() { super.viewDidLoad()
addListenerBlock(audioObjectPropertyListenerBlock,
onAudioObjectID: AudioObjectID(bitPattern: kAudioObjectSystemObject),
forPropertyAddress: AudioObjectPropertyAddress(
mSelector: kAudioHardwarePropertyDefaultOutputDevice,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMaster))
}
...
Provide a listener block function to receive the notifications. You're given an array that could potentially have more than one property address, so loop through and look for the one you want:
func audioObjectPropertyListenerBlock (numberAddresses: UInt32, addresses: UnsafePointer<AudioObjectPropertyAddress>) {
var index: UInt32 = 0
while index < numberAddresses {
let address: AudioObjectPropertyAddress = addresses[0]
switch address.mSelector {
case kAudioHardwarePropertyDefaultOutputDevice:
let deviceID = getDefaultAudioOutputDevice()
print("kAudioHardwarePropertyDefaultOutputDevice: \(deviceID)")
default:
print("We didn't expect this!")
}
index += 1
}
// Utility function to get default audio output device:
func getDefaultAudioOutputDevice () -> AudioObjectID {
var devicePropertyAddress = AudioObjectPropertyAddress(mSelector: kAudioHardwarePropertyDefaultOutputDevice, mScope: kAudioObjectPropertyScopeGlobal, mElement: kAudioObjectPropertyElementMaster)
var deviceID: AudioObjectID = 0
var dataSize = UInt32(truncatingBitPattern: sizeof(AudioDeviceID))
let systemObjectID = AudioObjectID(bitPattern: kAudioObjectSystemObject)
if (kAudioHardwareNoError != AudioObjectGetPropertyData(systemObjectID, &devicePropertyAddress, 0, nil, &dataSize, &deviceID)) { return 0 }
return deviceID
}
}
Of course, you can simply add more cases to the switch statement if you want to monitor other audio device properties. Call addListenerBlock to add whatever devices and property addresses you're interested in.
Related
as per the documentation, it should be pretty straightforward. example for a List: https://developer.apple.com/documentation/swiftui/list/ondrop(of:istargeted:perform:)-75hvy#
the UTType should be the parameter restricting what a SwiftUI object can receive. in my case i want to accept only Apps. the UTType is .applicationBundle: https://developer.apple.com/documentation/uniformtypeidentifiers/uttype/3551459-applicationbundle
but it doesn't work. the SwiftUI object never changes status and never accepts the drop. the closure is never run. whether on Lists, H/VStacks, Buttons, whatever. the pdf type don't seem to work either, as well as many others. the only type that i'm able to use if fileURL, which is mainly like no restriction.
i'm not sure if i'm doing something wrong or if SwiftUI is half working for the mac.
here's the code:
List(appsToIgnore, id: \.self, selection: $selection) {
Text($0)
}
.onDrop(of: [.applicationBundle, .application], isTargeted: isTargeted) { providers in
print("hehe")
return true
}
replacing or just adding .fileURL in the UTType array makes the drop work but without any type restriction.
i've also tried to use .onInsert on a ForEach instead (https://developer.apple.com/documentation/swiftui/foreach/oninsert(of:perform:)-2whxl#), and to go through a proper DropDelegate (https://developer.apple.com/documentation/swiftui/dropdelegate#) but keep getting the same results. it would seem the SwiftUI drop for macOS is not yet working, but i can't find any official information about this. in the docs it is written macOS 11.0+ so i would expect it to work?
any info appreciated! thanks.
You need to validate manually, using DropDelegate of what kind of file is dragged over.
Here is a simplified demo of possible approach. Tested with Xcode 13 / macOS 11.6
let delegate = MyDelegate()
...
List(appsToIgnore, id: \.self, selection: $selection) {
Text($0)
}
.onDrop(of: [.fileURL], delegate: delegate) // << accept file URLs
and verification part like
class MyDelegate: DropDelegate {
func validateDrop(info: DropInfo) -> Bool {
// find provider with file URL
guard info.hasItemsConforming(to: [.fileURL]) else { return false }
guard let provider = info.itemProviders(for: [.fileURL]).first else { return false }
var result = false
if provider.canLoadObject(ofClass: String.self) {
let group = DispatchGroup()
group.enter() // << make decoding sync
// decode URL from item provider
_ = provider.loadObject(ofClass: String.self) { value, _ in
defer { group.leave() }
guard let fileURL = value, let url = URL(string: fileURL) else { return }
// verify type of content by URL
let flag = try? url.resourceValues(forKeys: [.contentTypeKey]).contentType == .applicationBundle
result = flag ?? false
}
// wait a bit for verification result
_ = group.wait(timeout: .now() + 0.5)
}
return result
}
func performDrop(info: DropInfo) -> Bool {
// handling code is here
return true
}
}
Previous version of AudioKit had AKMicrophone.setDevice(_:), but since AKMicrophone was removed in v5, what is the correct way to get input from a device other than the default (on macOS)?
Here's what I've tried so far (map is called with a valid device ID, of that I'm sure):
func map(in: AudioDeviceID) {
let engine = AudioEngine()
let inUnit = engine.avEngine.inputNode.audioUnit!
setDevice(for: inUnit, to: `in`)
engine.output = engine.input
try! engine.start()
}
private func setDevice(for unit: AudioUnit, to id: AudioDeviceID) {
var deviceId = id;
let err = AudioUnitSetProperty(unit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &deviceId, UInt32(MemoryLayout<AudioDeviceID>.size))
if (err != noErr) {
print(err)
assert(false)
}
}
But when it calls engine.start() it crashes with:
2021-02-12 01:02:27.304545-0600 Roundabout[24616:587679] [avae] AVAEInternal.h:88 required condition is false: [AVAudioEngineGraph.mm:1408:Initialize: (IsFormatSampleRateAndChannelCountValid(outputHWFormat))]
Removing the setDevice call works, but then it's just passing the default microphone through, which is not what I want.
I'm having a little problem with my code who consists to play a sound when I start my app. But here's the problem everytime that I go back to the first screen the sound is playing again and I want it to play just one time. When the menu screen pop ups for the fist time.
Here's my code
var bubbleSound: SystemSoundID!
bubbleSound = createBubbleSound()
AudioServicesPlaySystemSound(bubbleSound)
(...)
the function
func createBubbleSound() -> SystemSoundID {
var soundID: SystemSoundID = 0
let soundURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(), "bubble", "wav", nil)
AudioServicesCreateSystemSoundID(soundURL, &soundID)
return soundID
}
You can define a struct like this (source):
struct MyViewState {
static var hasPlayedSound = false
}
Then in your viewDidLoad:
if(!MyViewState.hasPlayedSound) {
var bubbleSound: SystemSoundID!
bubbleSound = createBubbleSound()
AudioServicesPlaySystemSound(bubbleSound)
MyViewState.hasPlayedSound = true
}
You can then modify MyViewState.hasPlayedSound and allow the UIViewController to play the sound again if desired.
I'm trying to make an application that communicates with a USB device about the same way I use the screen command on Terminal.
To make my question easier to understand, This is what I normally do in Terminal :
command :
ls /dev/tty.usb*
returns :
/dev/tty.usbmodem1411 /dev/tty.usbmodem1451
Next, I call :
screen /dev/tty.usbmodem1411
After this, I can send commands to the device (type in 'U' for example, get a response)
I'm trying to do this from Xcode now.
Using IOKit, I've managed to execute what is equivalent to the first command that returns the list of usb ports :
/dev/tty.usbmodem1411 /dev/tty.usbmodem1451
This is the code :
#IBAction func testPressed(sender: AnyObject) {
var portIterator: io_iterator_t = 0
let kernResult = findSerialDevices(kIOSerialBSDModemType, serialPortIterator: &portIterator)
if kernResult == KERN_SUCCESS {
printSerialPaths(portIterator)
}
}
func findSerialDevices(deviceType: String, inout serialPortIterator: io_iterator_t ) -> kern_return_t {
var result: kern_return_t = KERN_FAILURE
var classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue).takeUnretainedValue()
var classesToMatchDict = (classesToMatch as NSDictionary) as Dictionary<String, AnyObject>
classesToMatchDict[kIOSerialBSDTypeKey] = deviceType
let classesToMatchCFDictRef = (classesToMatchDict as NSDictionary) as CFDictionaryRef
result = IOServiceGetMatchingServices(kIOMasterPortDefault, classesToMatchCFDictRef, &serialPortIterator);
return result
}
func printSerialPaths(portIterator: io_iterator_t) {
var serialService: io_object_t
do {
serialService = IOIteratorNext(portIterator)
if (serialService != 0) {
let key: CFString! = "IOCalloutDevice"
let bsdPathAsCFtring: AnyObject? = IORegistryEntryCreateCFProperty(serialService, key, kCFAllocatorDefault, 0).takeUnretainedValue()
var bsdPath = bsdPathAsCFtring as String?
if let path = bsdPath {
println(path)
}
}
} while serialService != 0;
}
}
Now, even after reading Apple's IOKit manual, I can't move forward.
How can I start sending commands using IOKit ?
See Apple Performing Serial I/O sample project.
I'm trying to update a progress bar with the progress of loading a load of values into CoreData. However, whenever I try to call an update on my progressView component, I get a fatal error stating that "unexpectedly found nil while unwrapping an Optional value".
The interesting thing is that this happens even if I put 'self.progressView.progress = 0.5' in the delegate method of my program - indicating that it's the progressView component it can't find rather than an issue with the value. A quick check with println also confirms the value does exist and so isn't nil. Note that if I put the 'self.progressView.progress = 0.5' statement under a function connected directly to a button, it works fine so it must be some sort of issue with the command being called from the delegate.
Can anyone work out what I'm doing wrong here? Thanks for your help.
Delegate method:
class ViewControllerUpdate: UIViewController, NSURLSessionDelegate, NSURLSessionDownloadDelegate, saveUpdate {
[....]
func updateStatus(status: String, progress: Float?) {
if let percentProgress = progress? {
self.progressView.progress = 0.5
}
//println(progress) - NOTE THIS IS CORRECTLY POPULATED WITH THE APPROPRIATE VALUE
}
Calling class:
protocol saveUpdate {
func updateStatus(status:String, progress:Float?)
}
class sqlPullSave {
let classtoUpdate: saveUpdate = ViewControllerUpdate()
func saveTSVtoSQL(fromFile: NSURL) -> Int {
//Load up the information into a Dictionary (tsv)
//let tsvURL = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource(fromFileName, ofType: fromFileExtension)!)
let tsvURL: NSURL = fromFile
let tab = NSCharacterSet(charactersInString: "\t")
let tsv = CSV(contentsOfURL: tsvURL, separator: tab)
//let defResult: AnyObject = tsv.rows[0]["Name"]!
//let tryagain:String = AnyObjecttoString(tsv.rows[1]["Name"]!)
//load the data into the SQLite database...
dispatch_async(dispatch_get_main_queue()) {
for a in 0..<tsv.rows.count {
self.SQLsaveLine(self.AnyObjecttoString(tsv.rows[a]["Name"]!),
name_l: "",
desc: self.AnyObjecttoString(tsv.rows[a]["1"]!),
jobTitle: self.AnyObjecttoString(tsv.rows[a]["2"]!),
extn: self.AnyObjecttoString(tsv.rows[a]["3"]!)
// update status
var percentComplete: Float = (Float(a) / Float(tsv.rows.count))
self.classtoUpdate.self.updateStatus("SQLload", progress: percentComplete)
}
}
return 0
}