mirror of
https://github.com/bitgapp/eqMac.git
synced 2024-11-22 13:07:26 +03:00
changes
This commit is contained in:
parent
12b990cd16
commit
e712cb55b1
@ -3,7 +3,7 @@
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 52;
|
||||
objectVersion = 47;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
@ -20,9 +20,12 @@
|
||||
78A804D726C4903E0021981C /* EQMDriver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78A804D626C4903E0021981C /* EQMDriver.swift */; };
|
||||
78A804DD26C563860021981C /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78A804DC26C563860021981C /* Constants.swift */; };
|
||||
78A804E026C5676D0021981C /* EQMPlugIn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78A804DF26C5676D0021981C /* EQMPlugIn.swift */; };
|
||||
78A804E426C58F3C0021981C /* Atomics in Frameworks */ = {isa = PBXBuildFile; productRef = 78A804E326C58F3C0021981C /* Atomics */; };
|
||||
78A804E726C595D80021981C /* EQMBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78A804E626C595D80021981C /* EQMBox.swift */; };
|
||||
78A804EA26C596080021981C /* EQMDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78A804E926C596080021981C /* EQMDevice.swift */; };
|
||||
78E8F7CE26D12B4C00B6B969 /* Volume.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E8F7CD26D12B4C00B6B969 /* Volume.swift */; };
|
||||
78E8F7D226D12BD500B6B969 /* AtomicCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E8F7D126D12BD500B6B969 /* AtomicCounter.swift */; };
|
||||
78E8F7D526D12C0100B6B969 /* EQMObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E8F7D426D12C0100B6B969 /* EQMObject.swift */; };
|
||||
78E8F7D926D166D800B6B969 /* EQMInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E8F7D826D166D800B6B969 /* EQMInterface.swift */; };
|
||||
E01A29F82456E50500369F41 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E01A29F72456E50500369F41 /* CoreFoundation.framework */; };
|
||||
E0DAAFBD21FE545F00DCCCEC /* icon.icns in Resources */ = {isa = PBXBuildFile; fileRef = E0DAAFBC21FE545F00DCCCEC /* icon.icns */; };
|
||||
/* End PBXBuildFile section */
|
||||
@ -47,6 +50,10 @@
|
||||
78A804DF26C5676D0021981C /* EQMPlugIn.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQMPlugIn.swift; sourceTree = "<group>"; };
|
||||
78A804E626C595D80021981C /* EQMBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQMBox.swift; sourceTree = "<group>"; };
|
||||
78A804E926C596080021981C /* EQMDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQMDevice.swift; sourceTree = "<group>"; };
|
||||
78E8F7CD26D12B4C00B6B969 /* Volume.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Volume.swift; path = ../shared/Volume.swift; sourceTree = "<group>"; };
|
||||
78E8F7D126D12BD500B6B969 /* AtomicCounter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AtomicCounter.swift; path = ../shared/AtomicCounter.swift; sourceTree = "<group>"; };
|
||||
78E8F7D426D12C0100B6B969 /* EQMObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQMObject.swift; sourceTree = "<group>"; };
|
||||
78E8F7D826D166D800B6B969 /* EQMInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EQMInterface.swift; sourceTree = "<group>"; };
|
||||
E01A29F72456E50500369F41 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
|
||||
E0DAAFBC21FE545F00DCCCEC /* icon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = icon.icns; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
@ -57,7 +64,6 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
E01A29F82456E50500369F41 /* CoreFoundation.framework in Frameworks */,
|
||||
78A804E426C58F3C0021981C /* Atomics in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -89,6 +95,8 @@
|
||||
78A8049C26C486350021981C /* Bridge */,
|
||||
78A804DC26C563860021981C /* Constants.swift */,
|
||||
78A804D626C4903E0021981C /* EQMDriver.swift */,
|
||||
78E8F7D826D166D800B6B969 /* EQMInterface.swift */,
|
||||
78E8F7D426D12C0100B6B969 /* EQMObject.swift */,
|
||||
78A804DF26C5676D0021981C /* EQMPlugIn.swift */,
|
||||
78A804E626C595D80021981C /* EQMBox.swift */,
|
||||
78A804E926C596080021981C /* EQMDevice.swift */,
|
||||
@ -117,6 +125,8 @@
|
||||
7838E34E26C897560039D466 /* SharedConstants.swift */,
|
||||
7838E35026C897560039D466 /* StringExtensions.swift */,
|
||||
7838E35E26C8A4190039D466 /* Utilities.swift */,
|
||||
78E8F7CD26D12B4C00B6B969 /* Volume.swift */,
|
||||
78E8F7D126D12BD500B6B969 /* AtomicCounter.swift */,
|
||||
);
|
||||
name = Shared;
|
||||
sourceTree = "<group>";
|
||||
@ -156,7 +166,6 @@
|
||||
);
|
||||
name = eqMac;
|
||||
packageProductDependencies = (
|
||||
78A804E326C58F3C0021981C /* Atomics */,
|
||||
);
|
||||
productName = EQMDriver;
|
||||
productReference = 1CB8B3641BBBB78D000E2DD1 /* eqMac.driver */;
|
||||
@ -189,7 +198,6 @@
|
||||
);
|
||||
mainGroup = 1CB8B3591BBBB69C000E2DD1;
|
||||
packageReferences = (
|
||||
78A804E226C58F3C0021981C /* XCRemoteSwiftPackageReference "swift-atomics" */,
|
||||
);
|
||||
productRefGroup = 1CB8B3651BBBB78D000E2DD1 /* Products */;
|
||||
projectDirPath = "";
|
||||
@ -226,10 +234,14 @@
|
||||
78A804E026C5676D0021981C /* EQMPlugIn.swift in Sources */,
|
||||
7838E35226C897560039D466 /* SharedConstants.swift in Sources */,
|
||||
78A804EA26C596080021981C /* EQMDevice.swift in Sources */,
|
||||
78E8F7D226D12BD500B6B969 /* AtomicCounter.swift in Sources */,
|
||||
7838E36426C9CC430039D466 /* ArrayExtensions.swift in Sources */,
|
||||
78E8F7D526D12C0100B6B969 /* EQMObject.swift in Sources */,
|
||||
78E8F7D926D166D800B6B969 /* EQMInterface.swift in Sources */,
|
||||
7838E35B26C898F30039D466 /* EQMControl.swift in Sources */,
|
||||
78A804DD26C563860021981C /* Constants.swift in Sources */,
|
||||
7838E35526C897560039D466 /* NumericTypesConversions.swift in Sources */,
|
||||
78E8F7CE26D12B4C00B6B969 /* Volume.swift in Sources */,
|
||||
78A804E726C595D80021981C /* EQMBox.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -392,9 +404,10 @@
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 1.2.3;
|
||||
DEAD_CODE_STRIPPING = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_NS_ASSERTIONS = YES;
|
||||
@ -402,7 +415,7 @@
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_ENABLE_CPP_RTTI = YES;
|
||||
GCC_INLINES_ARE_PRIVATE_EXTERN = NO;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = "";
|
||||
GCC_PREPROCESSOR_DEFINITIONS = "DEBUG=1";
|
||||
GCC_TREAT_WARNINGS_AS_ERRORS = NO;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
||||
@ -413,16 +426,13 @@
|
||||
INFOPLIST_FILE = "$(SRCROOT)/Supporting Files/Info.plist";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Audio/Plug-Ins/HAL";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = "";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
"@loader_path/../Frameworks",
|
||||
);
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||
MARKETING_VERSION = 1.2.3;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_CODE_SIGN_FLAGS = "";
|
||||
OTHER_SWIFT_FLAGS = "-D DEBUG";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.bitgapp.eqmac.driver;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@ -460,6 +470,7 @@
|
||||
CODE_SIGN_IDENTITY = "Developer ID Application";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
COPY_PHASE_STRIP = YES;
|
||||
CURRENT_PROJECT_VERSION = 1.2.3;
|
||||
DEAD_CODE_STRIPPING = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
@ -480,11 +491,7 @@
|
||||
INFOPLIST_FILE = "$(SRCROOT)/Supporting Files/Info.plist";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Audio/Plug-Ins/HAL";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = "";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
"@loader_path/../Frameworks",
|
||||
);
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||
MARKETING_VERSION = 1.2.3;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
@ -525,25 +532,6 @@
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
78A804E226C58F3C0021981C /* XCRemoteSwiftPackageReference "swift-atomics" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/apple/swift-atomics.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 0.0.3;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
78A804E326C58F3C0021981C /* Atomics */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 78A804E226C58F3C0021981C /* XCRemoteSwiftPackageReference "swift-atomics" */;
|
||||
productName = Atomics;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 1CB8B35A1BBBB69C000E2DD1 /* Project object */;
|
||||
}
|
||||
|
@ -35,7 +35,7 @@
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
debugAsWhichUser = "root"
|
||||
launchStyle = "0"
|
||||
launchStyle = "1"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
@ -46,7 +46,7 @@
|
||||
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
||||
<ActionContent
|
||||
title = "Run Script"
|
||||
scriptText = "export SUDO_ASKPASS=~/askpass.sh # Unload CoreAudio sudo -A launchctl unload /System/Library/LaunchDaemons/com.apple.audio.coreaudiod.plist # Uninstall any new driver leftovers sudo -A rm -rf /Library/Audio/Plug-Ins/HAL/eqMac.driver/ # Install the new driver sudo -A cp -f -r "$BUILT_PRODUCTS_DIR/$FULL_PRODUCT_NAME" /Library/Audio/Plug-Ins/HAL/ # Load CoreAudio #sudo -A launchctl load /System/Library/LaunchDaemons/com.apple.audio.coreaudiod.plist "
|
||||
scriptText = "export SUDO_ASKPASS=~/askpass.sh # Unload CoreAudio sudo -A launchctl unload /System/Library/LaunchDaemons/com.apple.audio.coreaudiod.plist # Uninstall any new driver leftovers sudo -A rm -rf /Library/Audio/Plug-Ins/HAL/eqMac.driver/ # Install the new driver sudo -A cp -f -r "$BUILT_PRODUCTS_DIR/$FULL_PRODUCT_NAME" /Library/Audio/Plug-Ins/HAL/ "
|
||||
shellToInvoke = "/bin/sh">
|
||||
<EnvironmentBuildable>
|
||||
<BuildableReference
|
||||
|
@ -9,6 +9,12 @@
|
||||
import Foundation
|
||||
import CoreAudio.AudioServerPlugIn
|
||||
|
||||
#if DEBUG
|
||||
let DEBUG = true
|
||||
#else
|
||||
let DEBUG = false
|
||||
#endif
|
||||
|
||||
let kAudioServerPluginTypeUUID = CFUUIDGetConstantUUIDWithBytes(nil, 0x44, 0x3A, 0xBA, 0xB8, 0xE7, 0xB3, 0x49, 0x1A, 0xB9, 0x85, 0xBE, 0xB9, 0x18, 0x70, 0x30, 0xDB)!
|
||||
let kAudioServerPluginDriverInterfaceUUID = CFUUIDGetConstantUUIDWithBytes(nil, 0xEE, 0xA5, 0x77, 0x3D, 0xCC, 0x43, 0x49, 0xF1, 0x8E, 0x00, 0x8F, 0x96, 0xE7, 0xD2, 0x3B, 0x17)!
|
||||
let IUnknownUUID = CFUUIDGetConstantUUIDWithBytes(kCFAllocatorSystemDefault, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4)!
|
||||
@ -21,9 +27,10 @@ let kDataSource_NumberItems: UInt32 = 1
|
||||
|
||||
let kDeviceName = "eqMac"
|
||||
let kDeviceManufacturer = "Bitgapp Ltd"
|
||||
let kBoxDefaultName = "eqMac Box"
|
||||
|
||||
let kPlugInBundleId = "com.bitgapp.eqmac.driver"
|
||||
let kBoxUID = "EQMBox"
|
||||
let kBoxUID = "eqMacBox_UID"
|
||||
let kDeviceUID = "EQMDevice"
|
||||
let kDeviceModelUID = "EQMDeviceModelUID"
|
||||
|
||||
@ -61,7 +68,7 @@ extension HRESULT {
|
||||
static let accessDenied = HRESULT(bitPattern: 0x80000009)
|
||||
}
|
||||
|
||||
let kObjectID_PlugIn: UInt32 = 1
|
||||
let kObjectID_PlugIn: UInt32 = kAudioObjectPlugInObject
|
||||
let kObjectID_Box: UInt32 = 2
|
||||
let kObjectID_Device: UInt32 = 3
|
||||
let kObjectID_Stream_Input: UInt32 = 4
|
||||
|
@ -9,9 +9,9 @@
|
||||
import Foundation
|
||||
import CoreAudio.AudioServerPlugIn
|
||||
|
||||
class EQMBox: EQMObjectProtocol {
|
||||
class EQMBox: EQMObject {
|
||||
static let id = AudioObjectID(kBoxUID)!
|
||||
static var name = "eqMac Box"
|
||||
static var name: String? = kBoxDefaultName
|
||||
static var acquired = false
|
||||
|
||||
static func hasProperty (objectID: AudioObjectID? = nil, address: AudioObjectPropertyAddress) -> Bool {
|
||||
@ -77,8 +77,7 @@ class EQMBox: EQMObjectProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
static func getPropertyData (objectID: AudioObjectID? = nil, address: AudioObjectPropertyAddress) -> EQMObjectProperty? {
|
||||
|
||||
static func getPropertyData (objectID: AudioObjectID? = nil, address: AudioObjectPropertyAddress, inData: UnsafeRawPointer?) -> EQMObjectProperty? {
|
||||
switch address.mSelector {
|
||||
case kAudioObjectPropertyBaseClass:
|
||||
// The base class for kAudioBoxClassID is kAudioObjectClassID
|
||||
@ -91,7 +90,7 @@ class EQMBox: EQMObjectProtocol {
|
||||
return .integer(kObjectID_PlugIn)
|
||||
case kAudioObjectPropertyName:
|
||||
// This is the human readable name of the maker of the box.
|
||||
return .string(name as CFString)
|
||||
return .string((name ?? kBoxDefaultName) as CFString)
|
||||
case kAudioObjectPropertyModelName:
|
||||
// This is the human readable name of the maker of the box.
|
||||
return .string("eqMac Model" as CFString)
|
||||
@ -143,7 +142,7 @@ class EQMBox: EQMObjectProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
static func setPropertyData(objectID: AudioObjectID? = nil, address: AudioObjectPropertyAddress, data: UnsafeRawPointer) -> OSStatus {
|
||||
static func setPropertyData(objectID: AudioObjectID? = nil, address: AudioObjectPropertyAddress, data: UnsafeRawPointer, changedProperties: inout [AudioObjectPropertyAddress]) -> OSStatus {
|
||||
switch address.mSelector {
|
||||
case kAudioObjectPropertyName:
|
||||
// Boxes should allow their name to be editable
|
||||
@ -163,7 +162,37 @@ class EQMBox: EQMObjectProtocol {
|
||||
if (acquired != wasAcquired) {
|
||||
// the new value is different from the old value, so save it
|
||||
acquired = wasAcquired
|
||||
|
||||
_ = EQMDriver.host?.pointee.WriteToStorage(EQMDriver.host!, "box acquired" as CFString, acquired ? kCFBooleanTrue : kCFBooleanFalse)
|
||||
|
||||
// and it means that this property and the device list property have changed
|
||||
changedProperties.append(
|
||||
AudioObjectPropertyAddress(
|
||||
mSelector: kAudioBoxPropertyAcquired,
|
||||
mScope: kAudioObjectPropertyScopeGlobal,
|
||||
mElement: kAudioObjectPropertyElementMaster
|
||||
)
|
||||
)
|
||||
|
||||
changedProperties.append(
|
||||
AudioObjectPropertyAddress(
|
||||
mSelector: kAudioBoxPropertyDeviceList,
|
||||
mScope: kAudioObjectPropertyScopeGlobal,
|
||||
mElement: kAudioObjectPropertyElementMaster
|
||||
)
|
||||
)
|
||||
|
||||
// but it also means that the device list has changed for the plug-in too
|
||||
DispatchQueue.global(qos: .default).async {
|
||||
var address = AudioObjectPropertyAddress(
|
||||
mSelector: kAudioPlugInPropertyDeviceList,
|
||||
mScope: kAudioObjectPropertyScopeGlobal,
|
||||
mElement: kAudioObjectPropertyElementMaster
|
||||
)
|
||||
_ = EQMDriver.host?.pointee.PropertiesChanged(EQMDriver.host!, kObjectID_PlugIn, 1, &address);
|
||||
}
|
||||
}
|
||||
|
||||
return noErr
|
||||
default: return kAudioHardwareUnknownPropertyError
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
import CoreAudio.AudioServerPlugIn
|
||||
|
||||
class EQMControl: EQMObjectProtocol {
|
||||
class EQMControl: EQMObject {
|
||||
static var outputVolume: Float32 = 1
|
||||
static var outputMuted = false
|
||||
static var inputDataSource: UInt32 = 0
|
||||
@ -148,7 +148,7 @@ class EQMControl: EQMObjectProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
static func getPropertyData (objectID: AudioObjectID?, address: AudioObjectPropertyAddress) -> EQMObjectProperty? {
|
||||
static func getPropertyData (objectID: AudioObjectID?, address: AudioObjectPropertyAddress, inData: UnsafeRawPointer?) -> EQMObjectProperty? {
|
||||
switch objectID {
|
||||
case kObjectID_Volume_Input_Master,
|
||||
kObjectID_Volume_Output_Master:
|
||||
@ -185,7 +185,7 @@ class EQMControl: EQMObjectProtocol {
|
||||
default: return 0
|
||||
}
|
||||
})()
|
||||
return .float32(volumeToScalar(volume))
|
||||
return .float32(Volume.toScalar(volume))
|
||||
case kAudioLevelControlPropertyDecibelValue:
|
||||
// This returns the dB value of the control.
|
||||
// Note that we need to take the state lock to examine the value.
|
||||
@ -196,7 +196,7 @@ class EQMControl: EQMObjectProtocol {
|
||||
default: return 0
|
||||
}
|
||||
})()
|
||||
return .float32(volumeToDecibel(volume))
|
||||
return .float32(Volume.toDecibel(volume))
|
||||
case kAudioLevelControlPropertyDecibelRange:
|
||||
// This returns the dB range of the control.
|
||||
return .valueRange(
|
||||
@ -311,7 +311,7 @@ class EQMControl: EQMObjectProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
static func setPropertyData(objectID: AudioObjectID?, address: AudioObjectPropertyAddress, data: UnsafeRawPointer) -> OSStatus {
|
||||
static func setPropertyData(objectID: AudioObjectID?, address: AudioObjectPropertyAddress, data: UnsafeRawPointer, changedProperties: inout [AudioObjectPropertyAddress]) -> OSStatus {
|
||||
switch objectID {
|
||||
case kObjectID_Volume_Input_Master,
|
||||
kObjectID_Volume_Output_Master:
|
||||
@ -323,7 +323,7 @@ class EQMControl: EQMObjectProtocol {
|
||||
return kAudioHardwareBadPropertySizeError
|
||||
}
|
||||
|
||||
var newVolume = scalarToVolume(scalar)
|
||||
var newVolume = Volume.fromScalar(scalar)
|
||||
newVolume = clamp(value: newVolume, min: 0.0, max: 1.0)
|
||||
|
||||
switch objectID {
|
||||
@ -348,7 +348,7 @@ class EQMControl: EQMObjectProtocol {
|
||||
}
|
||||
decibel = clamp(value: decibel, min: kMinVolumeDB, max: kMaxVolumeDB)
|
||||
|
||||
var newVolume = decibelToVolume(decibel)
|
||||
var newVolume = Volume.fromDecibel(decibel)
|
||||
newVolume = clamp(value: newVolume, min: 0.0, max: 1.0)
|
||||
|
||||
switch objectID {
|
||||
|
@ -8,9 +8,8 @@
|
||||
|
||||
import Foundation
|
||||
import CoreAudio.AudioServerPlugIn
|
||||
import Atomics
|
||||
|
||||
class EQMDevice: EQMObjectProtocol {
|
||||
class EQMDevice: EQMObject {
|
||||
static let id = AudioObjectID(kDeviceUID)!
|
||||
static var name = kDeviceName
|
||||
static var sampleRate = kDefaultSampleRate
|
||||
@ -18,7 +17,7 @@ class EQMDevice: EQMObjectProtocol {
|
||||
static var shown = false
|
||||
static var latency: UInt32 = 0
|
||||
static var ringBufferSize: UInt32 = 16384
|
||||
static var ioCounter = ManagedAtomic<UInt64>(0)
|
||||
static var ioCounter = AtomicCounter<UInt64>()
|
||||
static var hostTime: UInt64 = 0
|
||||
static var sampleTime: UInt64 = 0
|
||||
static var timestampCount: UInt64 = 0
|
||||
@ -124,18 +123,18 @@ class EQMDevice: EQMObjectProtocol {
|
||||
|
||||
case kAudioDevicePropertyZeroTimeStampPeriod: return sizeof(UInt32.self)
|
||||
case kAudioDevicePropertyIcon: return sizeof(CFURL.self)
|
||||
|
||||
case kAudioObjectPropertyCustomPropertyInfoList: return sizeof(AudioServerPlugInCustomPropertyInfo.self) * 3
|
||||
case kEQMDeviceCustomPropertyLatency: return sizeof(UInt32.self)
|
||||
case kEQMDeviceCustomPropertyShown: return sizeof(CFBoolean.self)
|
||||
case kEQMDeviceCustomPropertyVersion: return sizeof(CFString.self)
|
||||
case kAudioObjectPropertyCustomPropertyInfoList: return sizeof(AudioServerPlugInCustomPropertyInfo.self) * 4
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
static func getPropertyData (objectID: AudioObjectID? = nil, address: AudioObjectPropertyAddress) -> EQMObjectProperty? {
|
||||
|
||||
static func getPropertyData (objectID: AudioObjectID? = nil, address: AudioObjectPropertyAddress, inData: UnsafeRawPointer?) -> EQMObjectProperty? {
|
||||
switch address.mSelector {
|
||||
case kAudioObjectPropertyBaseClass:
|
||||
// The base class for kAudioDeviceClassID is kAudioObjectClassID
|
||||
@ -350,9 +349,7 @@ class EQMDevice: EQMObjectProtocol {
|
||||
return .url(url!)
|
||||
case kAudioObjectPropertyCustomPropertyInfoList:
|
||||
// This property returns an array of AudioServerPlugInCustomPropertyInfo's that
|
||||
// describe the type of data used by any custom properties. For this example,
|
||||
// the plug-in supports a single property whose data type is a CFString and
|
||||
// whose qualifier is a CFString.
|
||||
// describe the type of data used by any custom properties.
|
||||
let customProperties = ContiguousArray([
|
||||
AudioServerPlugInCustomPropertyInfo(
|
||||
mSelector: kEQMDeviceCustomPropertyVersion,
|
||||
@ -386,7 +383,7 @@ class EQMDevice: EQMObjectProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
static func setPropertyData(objectID: AudioObjectID? = nil, address: AudioObjectPropertyAddress, data: UnsafeRawPointer) -> OSStatus {
|
||||
static func setPropertyData(objectID: AudioObjectID? = nil, address: AudioObjectPropertyAddress, data: UnsafeRawPointer, changedProperties: inout [AudioObjectPropertyAddress]) -> OSStatus {
|
||||
switch address.mSelector {
|
||||
|
||||
case kAudioDevicePropertyNominalSampleRate:
|
||||
@ -440,7 +437,7 @@ class EQMDevice: EQMObjectProtocol {
|
||||
}
|
||||
|
||||
static func startIO () -> OSStatus {
|
||||
let ioCount = ioCounter.load(ordering: .relaxed)
|
||||
let ioCount = ioCounter.value
|
||||
|
||||
// Reached max amount of possible IOs
|
||||
if ioCount == UInt64.max {
|
||||
@ -456,7 +453,7 @@ class EQMDevice: EQMObjectProtocol {
|
||||
buffer = UnsafeMutablePointer<Float32>.allocate(capacity: Int(bufferSize * kChannelCount))
|
||||
} else {
|
||||
// IO already running so increment the counter
|
||||
ioCounter.wrappingIncrement(ordering: .relaxed)
|
||||
ioCounter.increment()
|
||||
}
|
||||
|
||||
return noErr
|
||||
@ -468,10 +465,11 @@ class EQMDevice: EQMObjectProtocol {
|
||||
return kAudioHardwareIllegalOperationError
|
||||
}
|
||||
|
||||
var ioCount = ioCounter.load(ordering: .relaxed)
|
||||
var ioCount = ioCounter.value
|
||||
|
||||
if ioCount > 0 {
|
||||
ioCount = ioCounter.wrappingDecrementThenLoad(ordering: .relaxed)
|
||||
ioCounter.decrement()
|
||||
ioCount = ioCounter.value
|
||||
}
|
||||
|
||||
// If IO reached zero deinit
|
||||
|
@ -8,23 +8,41 @@
|
||||
|
||||
import Foundation
|
||||
import CoreAudio.AudioServerPlugIn
|
||||
import Atomics
|
||||
|
||||
@objc
|
||||
class EQMDriver: NSObject {
|
||||
@objc class EQMDriver: NSObject {
|
||||
static var host: AudioServerPlugInHostRef?
|
||||
static var hostTicksPerFrame: Float64?
|
||||
|
||||
|
||||
static private var _interface: AudioServerPlugInDriverInterface?
|
||||
static private var _interfacePtr: UnsafeMutablePointer<AudioServerPlugInDriverInterface>?
|
||||
|
||||
static private var _ref: AudioServerPlugInDriverRef?
|
||||
static let refCounter = ManagedAtomic<UInt32>(0)
|
||||
static var ref: AudioServerPlugInDriverRef {
|
||||
if let ref = _ref {
|
||||
return ref
|
||||
|
||||
static var refCounter = AtomicCounter<UInt32>()
|
||||
|
||||
@objc public static var ref: AudioServerPlugInDriverRef?
|
||||
|
||||
@objc
|
||||
public static func create (allocator: CFAllocator!, requestedTypeUUID: CFUUID!) -> UnsafeMutableRawPointer? {
|
||||
// This is the CFPlugIn factory function. Its job is to create the implementation for the given
|
||||
// type provided that the type is supported. Because this driver is simple and all its
|
||||
// initialization is handled via static iniitalization when the bundle is loaded, all that
|
||||
// needs to be done is to return the AudioServerPlugInDriverRef that points to the driver's
|
||||
// interface. A more complicated driver would create any base line objects it needs to satisfy
|
||||
// the IUnknown methods that are used to discover that actual interface to talk to the driver.
|
||||
// The majority of the driver's initilization should be handled in the Initialize() method of
|
||||
// the driver's AudioServerPlugInDriverInterface.
|
||||
|
||||
if !CFEqual(requestedTypeUUID, kAudioServerPluginTypeUUID) {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
return UnsafeMutableRawPointer(createRef())
|
||||
}
|
||||
|
||||
private static func createRef () -> AudioServerPlugInDriverRef {
|
||||
if ref != nil {
|
||||
return ref!
|
||||
}
|
||||
|
||||
_interface = AudioServerPlugInDriverInterface(
|
||||
_reserved: nil,
|
||||
QueryInterface: EQM_QueryInterface,
|
||||
@ -50,424 +68,59 @@ class EQMDriver: NSObject {
|
||||
DoIOOperation: EQM_DoIOOperation,
|
||||
EndIOOperation: EQM_EndIOOperation
|
||||
)
|
||||
|
||||
|
||||
_interfacePtr = withUnsafeMutablePointer(to: &_interface!) { $0 }
|
||||
let ref = withUnsafeMutablePointer(to: &_interfacePtr) { $0 }
|
||||
_ref = ref
|
||||
|
||||
return ref
|
||||
ref = withUnsafeMutablePointer(to: &_interfacePtr) { $0 }
|
||||
|
||||
return ref!
|
||||
}
|
||||
|
||||
static func validateDriver (_ driverPointer: UnsafeMutableRawPointer?, reference: AudioServerPlugInDriverRef = EQMDriver.ref!) -> Bool {
|
||||
guard driverPointer != nil else { return false }
|
||||
let driver = driverPointer!.assumingMemoryBound(to: (UnsafeMutablePointer<AudioServerPlugInDriverInterface>?).self)
|
||||
let valid = reference == driver
|
||||
return valid
|
||||
}
|
||||
|
||||
@objc
|
||||
static func create (allocator: CFAllocator!, requestedTypeUUID: CFUUID!) -> UnsafeMutableRawPointer? {
|
||||
// This is the CFPlugIn factory function. Its job is to create the implementation for the given
|
||||
// type provided that the type is supported. Because this driver is simple and all its
|
||||
// initialization is handled via static iniitalization when the bundle is loaded, all that
|
||||
// needs to be done is to return the AudioServerPlugInDriverRef that points to the driver's
|
||||
// interface. A more complicated driver would create any base line objects it needs to satisfy
|
||||
// the IUnknown methods that are used to discover that actual interface to talk to the driver.
|
||||
// The majority of the driver's initilization should be handled in the Initialize() method of
|
||||
// the driver's AudioServerPlugInDriverInterface.
|
||||
|
||||
if (requestedTypeUUID != kAudioServerPluginTypeUUID) {
|
||||
return nil
|
||||
static func validateObject (_ objectID: AudioObjectID) -> Bool {
|
||||
if getEQMObject(from: objectID) != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return UnsafeMutableRawPointer(ref)
|
||||
}
|
||||
}
|
||||
|
||||
func validate (_ driverPointer: UnsafeMutableRawPointer?, reference: AudioServerPlugInDriverRef = EQMDriver.ref) -> Bool {
|
||||
if let driverPointer = driverPointer {
|
||||
let driver = driverPointer.assumingMemoryBound(to: (UnsafeMutablePointer<AudioServerPlugInDriverInterface>?).self)
|
||||
return reference == driver
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func calculateHostTicksPerFrame () -> Float64 {
|
||||
// calculate the host ticks per frame
|
||||
var theTimeBaseInfo = mach_timebase_info()
|
||||
mach_timebase_info(&theTimeBaseInfo)
|
||||
var theHostClockFrequency = Float64(theTimeBaseInfo.denom) / Float64(theTimeBaseInfo.numer)
|
||||
theHostClockFrequency *= 1000000000.0
|
||||
return theHostClockFrequency / EQMDevice.sampleRate
|
||||
}
|
||||
|
||||
// MARK: - Inheritance
|
||||
func EQM_QueryInterface (inDriver: UnsafeMutableRawPointer?, inUUID: REFIID, outInterface: UnsafeMutablePointer<LPVOID?>?) -> HRESULT {
|
||||
// This function is called by the HAL to get the interface to talk to the plug-in through.
|
||||
// AudioServerPlugIns are required to support the IUnknown interface and the
|
||||
// AudioServerPlugInDriverInterface. As it happens, all interfaces must also provide the
|
||||
// IUnknown interface, so we can always just return the single interface we made with
|
||||
// gAudioServerPlugInDriverInterfacePtr regardless of which one is asked for.
|
||||
guard validate(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
|
||||
|
||||
guard let uuid = CFUUIDCreateFromUUIDBytes(nil, inUUID), let interface = outInterface else {
|
||||
return kAudioHardwareIllegalOperationError
|
||||
log("Invalid object for ID: \(objectID)")
|
||||
return false
|
||||
}
|
||||
|
||||
guard uuid == IUnknownUUID || uuid == kAudioServerPluginTypeUUID else { return HRESULT.noInterface }
|
||||
|
||||
EQMDriver.refCounter.wrappingIncrement(ordering: .relaxed)
|
||||
interface.pointee = UnsafeMutableRawPointer(EQMDriver.ref)
|
||||
|
||||
return HRESULT.ok
|
||||
}
|
||||
|
||||
func EQM_AddRef (inDriver: UnsafeMutableRawPointer?) -> ULONG {
|
||||
// This call returns the resulting reference count after the increment.
|
||||
guard validate(inDriver) else { return ULONG(kAudioHardwareBadObjectError) }
|
||||
return EQMDriver.refCounter.wrappingIncrementThenLoad(ordering: .relaxed)
|
||||
}
|
||||
|
||||
func EQM_Release (inDriver: UnsafeMutableRawPointer?) -> ULONG {
|
||||
// This call returns the resulting reference count after the decrement.
|
||||
guard validate(inDriver) else { return ULONG(kAudioHardwareBadObjectError) }
|
||||
return EQMDriver.refCounter.wrappingDecrementThenLoad(ordering: .relaxed)
|
||||
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Basic Operations
|
||||
func EQM_Initialize (inDriver: AudioServerPlugInDriverRef, inHost: AudioServerPlugInHostRef) -> OSStatus {
|
||||
// This method is called to initialize the instance of the plug-in.
|
||||
// The job of this method is, as the name implies, to get the driver initialized. One specific
|
||||
// thing that needs to be done is to store the AudioServerPlugInHostRef so that it can be used
|
||||
// later. Note that when this call returns, the HAL will scan the various lists the driver
|
||||
// maintains (such as the device list) to get the inital set of objects the driver is
|
||||
// publishing. So, there is no need to notifiy the HAL about any objects created as part of the
|
||||
// execution of this method.
|
||||
guard validate(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
|
||||
EQMDriver.host = inHost
|
||||
EQMBox.acquired = true
|
||||
EQMDriver.hostTicksPerFrame = calculateHostTicksPerFrame()
|
||||
|
||||
return kAudioHardwareNoError
|
||||
}
|
||||
|
||||
func EQM_CreateDevice (inDriver: AudioServerPlugInDriverRef, inDescription: CFDictionary, inClientInfo: UnsafePointer<AudioServerPlugInClientInfo>, outDeviceObjectID: UnsafeMutablePointer<AudioObjectID>) -> OSStatus {
|
||||
// Tells the plug-in to create a new device based on the given description.
|
||||
// This method is used to tell a driver that implements the Transport Manager semantics to
|
||||
// create an AudioEndpointDevice from a set of AudioEndpoints. Since this driver is not a
|
||||
// Transport Manager, we just check the arguments and return
|
||||
// kAudioHardwareUnsupportedOperationError.
|
||||
guard validate(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
return kAudioHardwareUnsupportedOperationError
|
||||
}
|
||||
|
||||
func EQM_DestroyDevice (inDriver: AudioServerPlugInDriverRef, inDeviceObjectID: AudioObjectID) -> OSStatus {
|
||||
// Called to tell the plug-in about to destroy the given device.
|
||||
// This method is used to tell a driver that implements the Transport Manager semantics to
|
||||
// destroy an AudioEndpointDevice. Since this driver is not a Transport Manager, we just check
|
||||
// the arguments and return kAudioHardwareUnsupportedOperationError.
|
||||
guard validate(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
return kAudioHardwareUnsupportedOperationError
|
||||
}
|
||||
|
||||
func EQM_AddDeviceClient (inDriver: AudioServerPlugInDriverRef, inDeviceObjectID: AudioObjectID, inClientInfo: UnsafePointer<AudioServerPlugInClientInfo>) -> OSStatus {
|
||||
// Called to tell the plug-in about a new client of the Host for a particular device.
|
||||
// This method is used to inform the driver about a new client that is using the given device.
|
||||
// This allows the device to act differently depending on who the client is. This driver does
|
||||
// not need to track the clients using the device, so we just check the arguments and return
|
||||
// successfully.
|
||||
guard validate(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
return noErr
|
||||
}
|
||||
|
||||
func EQM_RemoveDeviceClient (inDriver: AudioServerPlugInDriverRef, inDeviceObjectID: AudioObjectID, inClientInfo: UnsafePointer<AudioServerPlugInClientInfo>) -> OSStatus {
|
||||
// Called to tell the plug-in about a client that is no longer using the device.
|
||||
// This method is used to inform the driver about a client that is no longer using the given
|
||||
// device. This driver does not track clients, so we just check the arguments and return
|
||||
// successfully.
|
||||
guard validate(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
return noErr
|
||||
}
|
||||
|
||||
func EQM_PerformDeviceConfigurationChange (inDriver: AudioServerPlugInDriverRef, inDeviceObjectID: AudioObjectID, inChangeAction: UInt64, inChangeInfo: UnsafeMutableRawPointer?) -> OSStatus {
|
||||
// This is called by the Host to allow the device to perform a configuration change that had been previously requested via a call to the Host method, RequestDeviceConfigurationChange().
|
||||
// This method is called to tell the device that it can perform the configuation change that it
|
||||
// had requested via a call to the host method, RequestDeviceConfigurationChange(). The
|
||||
// arguments, inChangeAction and inChangeInfo are the same as what was passed to
|
||||
// RequestDeviceConfigurationChange().
|
||||
//
|
||||
// The HAL guarantees that IO will be stopped while this method is in progress. The HAL will
|
||||
// also handle figuring out exactly what changed for the non-control related properties. This
|
||||
// means that the only notifications that would need to be sent here would be for either
|
||||
// custom properties the HAL doesn't know about or for controls.
|
||||
//
|
||||
// For the device implemented by this driver, only sample rate changes go through this process
|
||||
// as it is the only state that can be changed for the device that isn't a control. For this
|
||||
// change, the new sample rate is passed in the inChangeAction argument.
|
||||
|
||||
guard validate(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
|
||||
if !kSupportedSamplingRates.contains(where: { UInt64($0) == inChangeAction }) { return kAudioHardwareBadObjectError }
|
||||
|
||||
EQMDevice.sampleRate = Float64(inChangeAction)
|
||||
EQMDriver.hostTicksPerFrame = calculateHostTicksPerFrame()
|
||||
|
||||
return kAudioHardwareNoError
|
||||
}
|
||||
|
||||
func EQM_AbortDeviceConfigurationChange (inDriver: AudioServerPlugInDriverRef, inDeviceObjectID: AudioObjectID, inChangeAction: UInt64, inChangeInfo: UnsafeMutableRawPointer?) -> OSStatus {
|
||||
// This is called by the Host to tell the plug-in not to perform a configuration change that had been requested via a call to the Host method, RequestDeviceConfigurationChange().
|
||||
// This method is called to tell the driver that a request for a config change has been denied.
|
||||
// This provides the driver an opportunity to clean up any state associated with the request.
|
||||
// For this driver, an aborted config change requires no action. So we just check the arguments
|
||||
// and return
|
||||
guard validate(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
return noErr
|
||||
}
|
||||
|
||||
// MARK: - Property Operations
|
||||
// Note that for each object, this driver implements all the required properties plus a few
|
||||
// extras that are useful but not required. There is more detailed commentary about each
|
||||
// property in the EQMPlugin.getPropertyData() and EQMDevice.getPropertyData() methods.
|
||||
|
||||
func EQM_HasProperty (inDriver: AudioServerPlugInDriverRef, inObjectID: AudioObjectID, inClientProcessID: pid_t, inAddress: UnsafePointer<AudioObjectPropertyAddress>) -> DarwinBoolean {
|
||||
// This method returns whether or not the given object has the given property.
|
||||
guard validate(inDriver) else { return false }
|
||||
|
||||
return DarwinBoolean(({
|
||||
switch inObjectID {
|
||||
|
||||
case kObjectID_PlugIn: return EQMPlugIn.hasProperty(address: inAddress.pointee)
|
||||
case kObjectID_Box: return EQMBox.hasProperty(address: inAddress.pointee)
|
||||
case kObjectID_Device: return EQMDevice.hasProperty(address: inAddress.pointee)
|
||||
|
||||
static func getEQMObject(from objectID: AudioObjectID) -> EQMObject.Type? {
|
||||
switch objectID {
|
||||
case kObjectID_PlugIn: return EQMPlugIn.self
|
||||
case kObjectID_Box: return EQMBox.self
|
||||
case kObjectID_Device: return EQMDevice.self
|
||||
case kObjectID_Stream_Input,
|
||||
kObjectID_Stream_Output: return EQMStream.hasProperty(address: inAddress.pointee)
|
||||
|
||||
kObjectID_Stream_Output: return EQMStream.self
|
||||
case kObjectID_Volume_Input_Master,
|
||||
kObjectID_Volume_Output_Master,
|
||||
kObjectID_Mute_Input_Master,
|
||||
kObjectID_Mute_Output_Master,
|
||||
kObjectID_DataSource_Input_Master,
|
||||
kObjectID_DataSource_Output_Master: return EQMControl.hasProperty(objectID: inObjectID, address: inAddress.pointee)
|
||||
|
||||
default:
|
||||
return false
|
||||
kObjectID_DataSource_Output_Master: return EQMControl.self
|
||||
default: return nil
|
||||
}
|
||||
})())
|
||||
|
||||
}
|
||||
|
||||
func EQM_IsPropertySettable (inDriver: AudioServerPlugInDriverRef, inObjectID: AudioObjectID, inClientProcessID: pid_t, inAddress: UnsafePointer<AudioObjectPropertyAddress>, outIsSettable: UnsafeMutablePointer<DarwinBoolean>) -> OSStatus {
|
||||
// This method returns whether or not the given property on the object can have its value changed.
|
||||
guard validate(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
|
||||
let isSettable = DarwinBoolean(({
|
||||
switch inObjectID {
|
||||
|
||||
case kObjectID_PlugIn: return EQMPlugIn.isPropertySettable(address: inAddress.pointee)
|
||||
case kObjectID_Box: return EQMBox.isPropertySettable(address: inAddress.pointee)
|
||||
case kObjectID_Device: return EQMDevice.isPropertySettable(address: inAddress.pointee)
|
||||
|
||||
case kObjectID_Stream_Input,
|
||||
kObjectID_Stream_Output: return EQMStream.isPropertySettable(address: inAddress.pointee)
|
||||
|
||||
case kObjectID_Volume_Input_Master,
|
||||
kObjectID_Volume_Output_Master,
|
||||
kObjectID_Mute_Input_Master,
|
||||
kObjectID_Mute_Output_Master,
|
||||
kObjectID_DataSource_Input_Master,
|
||||
kObjectID_DataSource_Output_Master: return EQMControl.isPropertySettable(objectID: inObjectID, address: inAddress.pointee)
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
static func getEQMObjectClassName (from objectID: AudioObjectID) -> String {
|
||||
if let obj = getEQMObject(from: objectID) {
|
||||
return String(describing: obj.self)
|
||||
}
|
||||
})())
|
||||
|
||||
outIsSettable.pointee = isSettable
|
||||
|
||||
return noErr
|
||||
}
|
||||
|
||||
func EQM_GetPropertyDataSize (inDriver: AudioServerPlugInDriverRef, inObjectID: AudioObjectID, inClientProcessID: pid_t, inAddress: UnsafePointer<AudioObjectPropertyAddress>, inQualifierDataSize: UInt32, inQualifierData: UnsafeRawPointer?, outDataSize: UnsafeMutablePointer<UInt32>) -> OSStatus {
|
||||
// This method returns the byte size of the property's data.
|
||||
guard validate(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
|
||||
if let size = ({ () -> UInt32? in
|
||||
switch inObjectID {
|
||||
|
||||
case kObjectID_PlugIn: return EQMPlugIn.getPropertyDataSize(address: inAddress.pointee)
|
||||
case kObjectID_Box: return EQMBox.getPropertyDataSize(address: inAddress.pointee)
|
||||
case kObjectID_Device: return EQMDevice.getPropertyDataSize(address: inAddress.pointee)
|
||||
|
||||
case kObjectID_Stream_Input,
|
||||
kObjectID_Stream_Output: return EQMStream.getPropertyDataSize(address: inAddress.pointee)
|
||||
|
||||
case kObjectID_Volume_Input_Master,
|
||||
kObjectID_Volume_Output_Master,
|
||||
kObjectID_Mute_Input_Master,
|
||||
kObjectID_Mute_Output_Master,
|
||||
kObjectID_DataSource_Input_Master,
|
||||
kObjectID_DataSource_Output_Master: return EQMControl.getPropertyDataSize(objectID: inObjectID, address: inAddress.pointee)
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
})() {
|
||||
outDataSize.pointee = size
|
||||
return noErr
|
||||
} else {
|
||||
return kAudioHardwareUnknownPropertyError
|
||||
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
static func calculateHostTicksPerFrame () -> Float64 {
|
||||
// calculate the host ticks per frame
|
||||
var theTimeBaseInfo = mach_timebase_info()
|
||||
mach_timebase_info(&theTimeBaseInfo)
|
||||
var theHostClockFrequency = Float64(theTimeBaseInfo.denom) / Float64(theTimeBaseInfo.numer)
|
||||
theHostClockFrequency *= 1000000000.0
|
||||
return theHostClockFrequency / EQMDevice.sampleRate
|
||||
}
|
||||
}
|
||||
|
||||
func EQM_GetPropertyData (inDriver: AudioServerPlugInDriverRef, inObjectID: AudioObjectID, inClientProcessID: pid_t, inAddress: UnsafePointer<AudioObjectPropertyAddress>, inQualifierDataSize: UInt32, inQualifierData: UnsafeRawPointer?, inDataSize: UInt32, outDataSize: UnsafeMutablePointer<UInt32>, outData: UnsafeMutableRawPointer) -> OSStatus {
|
||||
// Fetches the data of the given property and places it in the provided buffer.
|
||||
guard validate(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
|
||||
if let data = ({ () -> EQMObjectProperty? in
|
||||
switch inObjectID {
|
||||
|
||||
case kObjectID_PlugIn: return EQMPlugIn.getPropertyData(address: inAddress.pointee)
|
||||
case kObjectID_Box: return EQMBox.getPropertyData(address: inAddress.pointee)
|
||||
case kObjectID_Device: return EQMDevice.getPropertyData(address: inAddress.pointee)
|
||||
|
||||
case kObjectID_Stream_Input,
|
||||
kObjectID_Stream_Output: return EQMStream.getPropertyData(objectID: inObjectID, address: inAddress.pointee)
|
||||
|
||||
case kObjectID_Volume_Input_Master,
|
||||
kObjectID_Volume_Output_Master,
|
||||
kObjectID_Mute_Input_Master,
|
||||
kObjectID_Mute_Output_Master,
|
||||
kObjectID_DataSource_Input_Master,
|
||||
kObjectID_DataSource_Output_Master: return EQMControl.getPropertyData(objectID: inObjectID, address: inAddress.pointee)
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
})() {
|
||||
data.write(to: outData, size: outDataSize)
|
||||
return noErr
|
||||
} else {
|
||||
outDataSize.pointee = 0
|
||||
return kAudioHardwareUnknownPropertyError
|
||||
}
|
||||
}
|
||||
|
||||
func EQM_SetPropertyData (inDriver: AudioServerPlugInDriverRef, inObjectID: AudioObjectID, inClientProcessID: pid_t, inAddress: UnsafePointer<AudioObjectPropertyAddress>, inQualifierDataSize: UInt32, inQualifierData: UnsafeRawPointer?, inDataSize: UInt32, inData: UnsafeRawPointer) -> OSStatus {
|
||||
// Tells an object to change the value of the given property.
|
||||
guard validate(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
switch inObjectID {
|
||||
|
||||
case kObjectID_PlugIn: return EQMPlugIn.setPropertyData(address: inAddress.pointee, data: inData)
|
||||
case kObjectID_Box: return EQMBox.setPropertyData(address: inAddress.pointee, data: inData)
|
||||
case kObjectID_Device: return EQMDevice.setPropertyData(address: inAddress.pointee, data: inData)
|
||||
|
||||
case kObjectID_Stream_Input,
|
||||
kObjectID_Stream_Output: return EQMStream.setPropertyData(objectID: inObjectID, address: inAddress.pointee, data: inData)
|
||||
|
||||
case kObjectID_Volume_Input_Master,
|
||||
kObjectID_Volume_Output_Master,
|
||||
kObjectID_Mute_Input_Master,
|
||||
kObjectID_Mute_Output_Master,
|
||||
kObjectID_DataSource_Input_Master,
|
||||
kObjectID_DataSource_Output_Master: return EQMControl.setPropertyData(objectID: inObjectID, address: inAddress.pointee, data: inData)
|
||||
|
||||
default:
|
||||
return kAudioHardwareBadObjectError
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - IO Operations
|
||||
func EQM_StartIO (inDriver: AudioServerPlugInDriverRef, inDeviceObjectID: AudioObjectID, inClientID: UInt32) -> OSStatus {
|
||||
// This call tells the device that IO is starting for the given client. When this routine
|
||||
// returns, the device's clock is running and it is ready to have data read/written. It is
|
||||
// important to note that multiple clients can have IO running on the device at the same time.
|
||||
// So, work only needs to be done when the first client starts. All subsequent starts simply
|
||||
// increment the counter.
|
||||
guard validate(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
|
||||
return EQMDevice.startIO()
|
||||
}
|
||||
|
||||
func EQM_StopIO (inDriver: AudioServerPlugInDriverRef, inDeviceObjectID: AudioObjectID, inClientID: UInt32) -> OSStatus {
|
||||
// This call tells the device that the client has stopped IO. The driver can stop the hardware
|
||||
// once all clients have stopped.
|
||||
guard validate(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
|
||||
return EQMDevice.stopIO()
|
||||
}
|
||||
|
||||
func EQM_GetZeroTimeStamp (inDriver: AudioServerPlugInDriverRef, inDeviceObjectID: AudioObjectID, inClientID: UInt32, outSampleTime: UnsafeMutablePointer<Float64>, outHostTime: UnsafeMutablePointer<UInt64>, outSeed: UnsafeMutablePointer<UInt64>) -> OSStatus {
|
||||
// This method returns the current zero time stamp for the device. The HAL models the timing of
|
||||
// a device as a series of time stamps that relate the sample time to a host time. The zero
|
||||
// time stamps are spaced such that the sample times are the value of
|
||||
// kAudioDevicePropertyZeroTimeStampPeriod apart. This is often modeled using a ring buffer
|
||||
// where the zero time stamp is updated when wrapping around the ring buffer.
|
||||
//
|
||||
// For this device, the zero time stamps' sample time increments every kDevice_RingBufferSize
|
||||
// frames and the host time increments by kDevice_RingBufferSize * gDevice_HostTicksPerFrame.
|
||||
guard validate(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
|
||||
return EQMDevice.getZeroTimeStamp(
|
||||
outSampleTime: outSampleTime,
|
||||
outHostTime: outHostTime,
|
||||
outSeed: outSeed
|
||||
)
|
||||
}
|
||||
|
||||
func EQM_WillDoIOOperation (inDriver: AudioServerPlugInDriverRef, inDeviceObjectID: AudioObjectID, inClientID: UInt32, inOperationID: UInt32, outWillDo: UnsafeMutablePointer<DarwinBoolean>, outWillDoInPlace: UnsafeMutablePointer<DarwinBoolean>) -> OSStatus {
|
||||
// Asks the plug-in whether or not the device will perform the given phase of the IO cycle for a particular device.
|
||||
// This method returns whether or not the device will do a given IO operation. For this device,
|
||||
// we only support reading input data and writing output data.
|
||||
guard validate(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
|
||||
var willDo = false
|
||||
var willDoInPlace = true
|
||||
|
||||
switch inOperationID {
|
||||
case AudioServerPlugInIOOperation.writeMix.rawValue:
|
||||
willDo = true
|
||||
willDoInPlace = true
|
||||
break
|
||||
case AudioServerPlugInIOOperation.readInput.rawValue:
|
||||
willDo = true
|
||||
willDoInPlace = true
|
||||
break
|
||||
default: break
|
||||
}
|
||||
|
||||
outWillDo.pointee = DarwinBoolean(willDo)
|
||||
outWillDoInPlace.pointee = DarwinBoolean(willDoInPlace)
|
||||
|
||||
return noErr
|
||||
}
|
||||
|
||||
func EQM_BeginIOOperation (inDriver: AudioServerPlugInDriverRef, inDeviceObjectID: AudioObjectID, inClientID: UInt32, inOperationID: UInt32, inIOBufferFrameSize: UInt32, inIOCycleInfo: UnsafePointer<AudioServerPlugInIOCycleInfo>) -> OSStatus {
|
||||
// Tells the plug-in that the Host is about to begin a phase of the IO cycle for a particular device.
|
||||
// This is called at the beginning of an IO operation. This device doesn't do anything, so just
|
||||
// check the arguments and return.
|
||||
guard validate(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
return noErr
|
||||
}
|
||||
|
||||
func EQM_DoIOOperation (inDriver: AudioServerPlugInDriverRef, inDeviceObjectID: AudioObjectID, inStreamObjectID: AudioObjectID, inClientID: UInt32, inOperationID: UInt32, inIOBufferFrameSize: UInt32, inIOCycleInfo: UnsafePointer<AudioServerPlugInIOCycleInfo>, ioMainBuffer: UnsafeMutableRawPointer?, ioSecondaryBuffer: UnsafeMutableRawPointer?) -> OSStatus {
|
||||
// This is called to actuall perform a given operation.
|
||||
guard validate(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
|
||||
guard let sample = ioMainBuffer?.assumingMemoryBound(to: Float32.self) else {
|
||||
return noErr
|
||||
}
|
||||
|
||||
return EQMDevice.doIO(
|
||||
clientID: inClientID,
|
||||
operationID: inOperationID,
|
||||
sample: sample,
|
||||
sampleTime: Int(inIOCycleInfo.pointee.mInputTime.mSampleTime),
|
||||
frameSize: inIOBufferFrameSize
|
||||
)
|
||||
}
|
||||
|
||||
func EQM_EndIOOperation (inDriver: AudioServerPlugInDriverRef, inDeviceObjectID: AudioObjectID, inClientID: UInt32, inOperationID: UInt32, inIOBufferFrameSize: UInt32, inIOCycleInfo: UnsafePointer<AudioServerPlugInIOCycleInfo>) -> OSStatus {
|
||||
// This is called at the end of an IO operation. This device doesn't do anything, so just check
|
||||
// the arguments and return.
|
||||
guard validate(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
return noErr
|
||||
}
|
||||
|
371
native/driver/Source/EQMInterface.swift
Normal file
371
native/driver/Source/EQMInterface.swift
Normal file
@ -0,0 +1,371 @@
|
||||
//
|
||||
// EQMInterface.swift
|
||||
// eqMac
|
||||
//
|
||||
// Created by Romans Kisils on 21/08/2021.
|
||||
// Copyright © 2021 Bitgapp. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreAudio.AudioServerPlugIn
|
||||
|
||||
// MARK: - Inheritance
|
||||
func EQM_QueryInterface (inDriver: UnsafeMutableRawPointer?, inUUID: REFIID, outInterface: UnsafeMutablePointer<LPVOID?>?) -> HRESULT {
|
||||
// This function is called by the HAL to get the interface to talk to the plug-in through.
|
||||
// AudioServerPlugIns are required to support the IUnknown interface and the
|
||||
// AudioServerPlugInDriverInterface. As it happens, all interfaces must also provide the
|
||||
// IUnknown interface, so we can always just return the single interface we made with
|
||||
// gAudioServerPlugInDriverInterfacePtr regardless of which one is asked for.
|
||||
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
|
||||
let uuid = CFUUIDCreateFromUUIDBytes(nil, inUUID)
|
||||
if uuid == nil || outInterface == nil {
|
||||
return kAudioHardwareIllegalOperationError
|
||||
}
|
||||
|
||||
guard CFEqual(uuid, IUnknownUUID) || CFEqual(uuid, kAudioServerPluginDriverInterfaceUUID) else { return HRESULT.noInterface }
|
||||
|
||||
EQMDriver.refCounter.increment()
|
||||
outInterface!.pointee = UnsafeMutableRawPointer(EQMDriver.ref)
|
||||
|
||||
return HRESULT.ok
|
||||
}
|
||||
|
||||
func EQM_AddRef (inDriver: UnsafeMutableRawPointer?) -> ULONG {
|
||||
// This call returns the resulting reference count after the increment.
|
||||
guard EQMDriver.validateDriver(inDriver) else { return ULONG(kAudioHardwareBadObjectError) }
|
||||
EQMDriver.refCounter.increment()
|
||||
return EQMDriver.refCounter.value
|
||||
}
|
||||
|
||||
func EQM_Release (inDriver: UnsafeMutableRawPointer?) -> ULONG {
|
||||
// This call returns the resulting reference count after the decrement.
|
||||
guard EQMDriver.validateDriver(inDriver) else { return ULONG(kAudioHardwareBadObjectError) }
|
||||
EQMDriver.refCounter.decrement()
|
||||
return EQMDriver.refCounter.value
|
||||
}
|
||||
|
||||
// MARK: - Basic Operations
|
||||
func EQM_Initialize (inDriver: AudioServerPlugInDriverRef, inHost: AudioServerPlugInHostRef) -> OSStatus {
|
||||
// This method is called to initialize the instance of the plug-in.
|
||||
// The job of this method is, as the name implies, to get the driver initialized. One specific
|
||||
// thing that needs to be done is to store the AudioServerPlugInHostRef so that it can be used
|
||||
// later. Note that when this call returns, the HAL will scan the various lists the driver
|
||||
// maintains (such as the device list) to get the inital set of objects the driver is
|
||||
// publishing. So, there is no need to notifiy the HAL about any objects created as part of the
|
||||
// execution of this method.
|
||||
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
|
||||
EQMDriver.host = inHost
|
||||
|
||||
// initialize the box acquired property from the settings
|
||||
var settingsPointer: Unmanaged<CFPropertyList>?
|
||||
_ = EQMDriver.host!.pointee.CopyFromStorage(EQMDriver.host!, "box acquired" as CFString, &settingsPointer)
|
||||
|
||||
if let settings = settingsPointer?.takeUnretainedValue() {
|
||||
if (CFGetTypeID(settings) == CFBooleanGetTypeID()) {
|
||||
EQMBox.acquired = CFBooleanGetValue((settings as! CFBoolean))
|
||||
} else if (CFGetTypeID(settings) == CFNumberGetTypeID()) {
|
||||
var value: Int32 = 0
|
||||
CFNumberGetValue((settings as! CFNumber), .sInt32Type, &value)
|
||||
EQMBox.acquired = value == 1
|
||||
}
|
||||
}
|
||||
|
||||
// initialize the box name from the settings
|
||||
_ = EQMDriver.host!.pointee.CopyFromStorage(EQMDriver.host!, "box acquired" as CFString, &settingsPointer)
|
||||
if let settings = settingsPointer?.takeUnretainedValue() {
|
||||
if (CFGetTypeID(settings) == CFStringGetTypeID()) {
|
||||
EQMBox.name = (settings as! CFString) as String
|
||||
}
|
||||
}
|
||||
|
||||
// set the box name directly as a last resort
|
||||
if (EQMBox.name == nil) {
|
||||
EQMBox.name = kBoxDefaultName
|
||||
}
|
||||
|
||||
EQMBox.acquired = true
|
||||
EQMDriver.hostTicksPerFrame = EQMDriver.calculateHostTicksPerFrame()
|
||||
|
||||
return kAudioHardwareNoError
|
||||
}
|
||||
|
||||
func EQM_CreateDevice (inDriver: AudioServerPlugInDriverRef, inDescription: CFDictionary, inClientInfo: UnsafePointer<AudioServerPlugInClientInfo>, outDeviceObjectID: UnsafeMutablePointer<AudioObjectID>) -> OSStatus {
|
||||
// Tells the plug-in to create a new device based on the given description.
|
||||
// This method is used to tell a driver that implements the Transport Manager semantics to
|
||||
// create an AudioEndpointDevice from a set of AudioEndpoints. Since this driver is not a
|
||||
// Transport Manager, we just check the arguments and return
|
||||
// kAudioHardwareUnsupportedOperationError.
|
||||
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
return kAudioHardwareUnsupportedOperationError
|
||||
}
|
||||
|
||||
func EQM_DestroyDevice (inDriver: AudioServerPlugInDriverRef, inDeviceObjectID: AudioObjectID) -> OSStatus {
|
||||
// Called to tell the plug-in about to destroy the given device.
|
||||
// This method is used to tell a driver that implements the Transport Manager semantics to
|
||||
// destroy an AudioEndpointDevice. Since this driver is not a Transport Manager, we just check
|
||||
// the arguments and return kAudioHardwareUnsupportedOperationError.
|
||||
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
return kAudioHardwareUnsupportedOperationError
|
||||
}
|
||||
|
||||
func EQM_AddDeviceClient (inDriver: AudioServerPlugInDriverRef, inDeviceObjectID: AudioObjectID, inClientInfo: UnsafePointer<AudioServerPlugInClientInfo>) -> OSStatus {
|
||||
// Called to tell the plug-in about a new client of the Host for a particular device.
|
||||
// This method is used to inform the driver about a new client that is using the given device.
|
||||
// This allows the device to act differently depending on who the client is. This driver does
|
||||
// not need to track the clients using the device, so we just check the arguments and return
|
||||
// successfully.
|
||||
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
return noErr
|
||||
}
|
||||
|
||||
func EQM_RemoveDeviceClient (inDriver: AudioServerPlugInDriverRef, inDeviceObjectID: AudioObjectID, inClientInfo: UnsafePointer<AudioServerPlugInClientInfo>) -> OSStatus {
|
||||
// Called to tell the plug-in about a client that is no longer using the device.
|
||||
// This method is used to inform the driver about a client that is no longer using the given
|
||||
// device. This driver does not track clients, so we just check the arguments and return
|
||||
// successfully.
|
||||
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
return noErr
|
||||
}
|
||||
|
||||
func EQM_PerformDeviceConfigurationChange (inDriver: AudioServerPlugInDriverRef, inDeviceObjectID: AudioObjectID, inChangeAction: UInt64, inChangeInfo: UnsafeMutableRawPointer?) -> OSStatus {
|
||||
// This is called by the Host to allow the device to perform a configuration change that had been previously requested via a call to the Host method, RequestDeviceConfigurationChange().
|
||||
// This method is called to tell the device that it can perform the configuation change that it
|
||||
// had requested via a call to the host method, RequestDeviceConfigurationChange(). The
|
||||
// arguments, inChangeAction and inChangeInfo are the same as what was passed to
|
||||
// RequestDeviceConfigurationChange().
|
||||
//
|
||||
// The HAL guarantees that IO will be stopped while this method is in progress. The HAL will
|
||||
// also handle figuring out exactly what changed for the non-control related properties. This
|
||||
// means that the only notifications that would need to be sent here would be for either
|
||||
// custom properties the HAL doesn't know about or for controls.
|
||||
//
|
||||
// For the device implemented by this driver, only sample rate changes go through this process
|
||||
// as it is the only state that can be changed for the device that isn't a control. For this
|
||||
// change, the new sample rate is passed in the inChangeAction argument.
|
||||
|
||||
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
|
||||
if !kSupportedSamplingRates.contains(where: { UInt64($0) == inChangeAction }) { return kAudioHardwareBadObjectError }
|
||||
|
||||
EQMDevice.sampleRate = Float64(inChangeAction)
|
||||
EQMDriver.hostTicksPerFrame = EQMDriver.calculateHostTicksPerFrame()
|
||||
|
||||
return kAudioHardwareNoError
|
||||
}
|
||||
|
||||
func EQM_AbortDeviceConfigurationChange (inDriver: AudioServerPlugInDriverRef, inDeviceObjectID: AudioObjectID, inChangeAction: UInt64, inChangeInfo: UnsafeMutableRawPointer?) -> OSStatus {
|
||||
// This is called by the Host to tell the plug-in not to perform a configuration change that had been requested via a call to the Host method, RequestDeviceConfigurationChange().
|
||||
// This method is called to tell the driver that a request for a config change has been denied.
|
||||
// This provides the driver an opportunity to clean up any state associated with the request.
|
||||
// For this driver, an aborted config change requires no action. So we just check the arguments
|
||||
// and return
|
||||
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
return noErr
|
||||
}
|
||||
|
||||
// MARK: - Property Operations
|
||||
// Note that for each object, this driver implements all the required properties plus a few
|
||||
// extras that are useful but not required. There is more detailed commentary about each
|
||||
// property in the EQMPlugin.getPropertyData() and EQMDevice.getPropertyData() methods.
|
||||
|
||||
func EQM_HasProperty (inDriver: AudioServerPlugInDriverRef, inObjectID: AudioObjectID, inClientProcessID: pid_t, inAddress: UnsafePointer<AudioObjectPropertyAddress>) -> DarwinBoolean {
|
||||
// This method returns whether or not the given object has the given property.
|
||||
|
||||
log("Invoking: \(EQMDriver.getEQMObjectClassName(from: inObjectID)).hasProperty(\(inAddress.pointee.mSelector.code))")
|
||||
let hasProperty: Bool = ({
|
||||
guard EQMDriver.validateDriver(inDriver) else { return false }
|
||||
if let obj = EQMDriver.getEQMObject(from: inObjectID) {
|
||||
return obj.hasProperty(objectID: inObjectID, address: inAddress.pointee)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})()
|
||||
log("\(EQMDriver.getEQMObjectClassName(from: inObjectID)).hasProperty(\(inAddress.pointee.mSelector.code)) = \(hasProperty)")
|
||||
|
||||
return DarwinBoolean(hasProperty)
|
||||
}
|
||||
|
||||
func EQM_IsPropertySettable (inDriver: AudioServerPlugInDriverRef, inObjectID: AudioObjectID, inClientProcessID: pid_t, inAddress: UnsafePointer<AudioObjectPropertyAddress>, outIsSettable: UnsafeMutablePointer<DarwinBoolean>) -> OSStatus {
|
||||
// This method returns whether or not the given property on the object can have its value changed.
|
||||
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
|
||||
log("Invoking: \(EQMDriver.getEQMObjectClassName(from: inObjectID)).isPropertySettable(\(inAddress.pointee.mSelector.code))")
|
||||
let isSettable = DarwinBoolean(({
|
||||
if let obj = EQMDriver.getEQMObject(from: inObjectID) {
|
||||
return obj.isPropertySettable(objectID: inObjectID, address: inAddress.pointee)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})())
|
||||
log("\(EQMDriver.getEQMObjectClassName(from: inObjectID)).getPropertySettable(\(inAddress.pointee.mSelector.code)) = \(isSettable)")
|
||||
|
||||
outIsSettable.pointee = isSettable
|
||||
|
||||
return noErr
|
||||
}
|
||||
|
||||
func EQM_GetPropertyDataSize (inDriver: AudioServerPlugInDriverRef, inObjectID: AudioObjectID, inClientProcessID: pid_t, inAddress: UnsafePointer<AudioObjectPropertyAddress>, inQualifierDataSize: UInt32, inQualifierData: UnsafeRawPointer?, outDataSize: UnsafeMutablePointer<UInt32>) -> OSStatus {
|
||||
// This method returns the byte size of the property's data.
|
||||
guard EQMDriver.validateDriver(inDriver) && EQMDriver.validateObject(inObjectID) else { return kAudioHardwareBadObjectError }
|
||||
|
||||
log("Invoking: \(EQMDriver.getEQMObjectClassName(from: inObjectID)).getPropertyDataSize(\(inAddress.pointee.mSelector.code))")
|
||||
let size = ({ () -> UInt32? in
|
||||
if let obj = EQMDriver.getEQMObject(from: inObjectID) {
|
||||
return obj.getPropertyDataSize(objectID: inObjectID, address: inAddress.pointee)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
})()
|
||||
log("\(EQMDriver.getEQMObjectClassName(from: inObjectID)).getPropertyDataSize(\(inAddress.pointee.mSelector.code)) = \(size ?? 0)")
|
||||
|
||||
if size != nil {
|
||||
outDataSize.pointee = size!
|
||||
return noErr
|
||||
} else {
|
||||
return kAudioHardwareUnknownPropertyError
|
||||
}
|
||||
}
|
||||
|
||||
func EQM_GetPropertyData (inDriver: AudioServerPlugInDriverRef, inObjectID: AudioObjectID, inClientProcessID: pid_t, inAddress: UnsafePointer<AudioObjectPropertyAddress>, inQualifierDataSize: UInt32, inQualifierData: UnsafeRawPointer?, inDataSize: UInt32, outDataSize: UnsafeMutablePointer<UInt32>, outData: UnsafeMutableRawPointer) -> OSStatus {
|
||||
// Fetches the data of the given property and places it in the provided buffer.
|
||||
guard EQMDriver.validateDriver(inDriver) && EQMDriver.validateObject(inObjectID) else {
|
||||
log("Invalid driver or object id while in EQM_GetPropertyData(\(inAddress.pointee.mSelector.code))")
|
||||
return kAudioHardwareBadObjectError
|
||||
}
|
||||
|
||||
log("Invoking: \(EQMDriver.getEQMObjectClassName(from: inObjectID)).getPropertyData(\(inAddress.pointee.mSelector.code))")
|
||||
let data = ({ () -> EQMObjectProperty? in
|
||||
if let obj = EQMDriver.getEQMObject(from: inObjectID) {
|
||||
return obj.getPropertyData(objectID: inObjectID, address: inAddress.pointee, inData: inQualifierData)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
})()
|
||||
log("\(EQMDriver.getEQMObjectClassName(from: inObjectID)).getPropertyData(\(inAddress.pointee.mSelector.code)) = \(String(describing: data))")
|
||||
if data != nil {
|
||||
data!.write(to: outData, size: outDataSize, requestedSize: inDataSize)
|
||||
return noErr
|
||||
} else {
|
||||
outDataSize.pointee = 0
|
||||
return kAudioHardwareUnknownPropertyError
|
||||
}
|
||||
}
|
||||
|
||||
func EQM_SetPropertyData (inDriver: AudioServerPlugInDriverRef, inObjectID: AudioObjectID, inClientProcessID: pid_t, inAddress: UnsafePointer<AudioObjectPropertyAddress>, inQualifierDataSize: UInt32, inQualifierData: UnsafeRawPointer?, inDataSize: UInt32, inData: UnsafeRawPointer) -> OSStatus {
|
||||
// Tells an object to change the value of the given property.
|
||||
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
|
||||
log("\(EQMDriver.getEQMObjectClassName(from: inObjectID)).setPropertyData(\(inAddress.pointee.mSelector.code)) = \(String(describing: inData))")
|
||||
|
||||
if let obj = EQMDriver.getEQMObject(from: inObjectID) {
|
||||
var changedProperties: [AudioObjectPropertyAddress] = []
|
||||
let status = obj.setPropertyData(objectID: inObjectID, address: inAddress.pointee, data: inData, changedProperties: &changedProperties)
|
||||
|
||||
if changedProperties.count > 0 {
|
||||
_ = EQMDriver.host!.pointee.PropertiesChanged(EQMDriver.host!, inObjectID, UInt32(changedProperties.count), changedProperties)
|
||||
}
|
||||
|
||||
return status
|
||||
} else {
|
||||
return kAudioHardwareBadObjectError
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - IO Operations
|
||||
func EQM_StartIO (inDriver: AudioServerPlugInDriverRef, inDeviceObjectID: AudioObjectID, inClientID: UInt32) -> OSStatus {
|
||||
// This call tells the device that IO is starting for the given client. When this routine
|
||||
// returns, the device's clock is running and it is ready to have data read/written. It is
|
||||
// important to note that multiple clients can have IO running on the device at the same time.
|
||||
// So, work only needs to be done when the first client starts. All subsequent starts simply
|
||||
// increment the counter.
|
||||
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
|
||||
return EQMDevice.startIO()
|
||||
}
|
||||
|
||||
func EQM_StopIO (inDriver: AudioServerPlugInDriverRef, inDeviceObjectID: AudioObjectID, inClientID: UInt32) -> OSStatus {
|
||||
// This call tells the device that the client has stopped IO. The driver can stop the hardware
|
||||
// once all clients have stopped.
|
||||
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
|
||||
return EQMDevice.stopIO()
|
||||
}
|
||||
|
||||
func EQM_GetZeroTimeStamp (inDriver: AudioServerPlugInDriverRef, inDeviceObjectID: AudioObjectID, inClientID: UInt32, outSampleTime: UnsafeMutablePointer<Float64>, outHostTime: UnsafeMutablePointer<UInt64>, outSeed: UnsafeMutablePointer<UInt64>) -> OSStatus {
|
||||
// This method returns the current zero time stamp for the device. The HAL models the timing of
|
||||
// a device as a series of time stamps that relate the sample time to a host time. The zero
|
||||
// time stamps are spaced such that the sample times are the value of
|
||||
// kAudioDevicePropertyZeroTimeStampPeriod apart. This is often modeled using a ring buffer
|
||||
// where the zero time stamp is updated when wrapping around the ring buffer.
|
||||
//
|
||||
// For this device, the zero time stamps' sample time increments every kDevice_RingBufferSize
|
||||
// frames and the host time increments by kDevice_RingBufferSize * gDevice_HostTicksPerFrame.
|
||||
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
|
||||
return EQMDevice.getZeroTimeStamp(
|
||||
outSampleTime: outSampleTime,
|
||||
outHostTime: outHostTime,
|
||||
outSeed: outSeed
|
||||
)
|
||||
}
|
||||
|
||||
func EQM_WillDoIOOperation (inDriver: AudioServerPlugInDriverRef, inDeviceObjectID: AudioObjectID, inClientID: UInt32, inOperationID: UInt32, outWillDo: UnsafeMutablePointer<DarwinBoolean>, outWillDoInPlace: UnsafeMutablePointer<DarwinBoolean>) -> OSStatus {
|
||||
// Asks the plug-in whether or not the device will perform the given phase of the IO cycle for a particular device.
|
||||
// This method returns whether or not the device will do a given IO operation. For this device,
|
||||
// we only support reading input data and writing output data.
|
||||
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
|
||||
var willDo = false
|
||||
var willDoInPlace = true
|
||||
|
||||
switch inOperationID {
|
||||
case AudioServerPlugInIOOperation.writeMix.rawValue:
|
||||
willDo = true
|
||||
willDoInPlace = true
|
||||
break
|
||||
case AudioServerPlugInIOOperation.readInput.rawValue:
|
||||
willDo = true
|
||||
willDoInPlace = true
|
||||
break
|
||||
default: break
|
||||
}
|
||||
|
||||
outWillDo.pointee = DarwinBoolean(willDo)
|
||||
outWillDoInPlace.pointee = DarwinBoolean(willDoInPlace)
|
||||
|
||||
return noErr
|
||||
}
|
||||
|
||||
func EQM_BeginIOOperation (inDriver: AudioServerPlugInDriverRef, inDeviceObjectID: AudioObjectID, inClientID: UInt32, inOperationID: UInt32, inIOBufferFrameSize: UInt32, inIOCycleInfo: UnsafePointer<AudioServerPlugInIOCycleInfo>) -> OSStatus {
|
||||
// Tells the plug-in that the Host is about to begin a phase of the IO cycle for a particular device.
|
||||
// This is called at the beginning of an IO operation. This device doesn't do anything, so just
|
||||
// check the arguments and return.
|
||||
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
return noErr
|
||||
}
|
||||
|
||||
func EQM_DoIOOperation (inDriver: AudioServerPlugInDriverRef, inDeviceObjectID: AudioObjectID, inStreamObjectID: AudioObjectID, inClientID: UInt32, inOperationID: UInt32, inIOBufferFrameSize: UInt32, inIOCycleInfo: UnsafePointer<AudioServerPlugInIOCycleInfo>, ioMainBuffer: UnsafeMutableRawPointer?, ioSecondaryBuffer: UnsafeMutableRawPointer?) -> OSStatus {
|
||||
// This is called to actuall perform a given operation.
|
||||
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
|
||||
guard let sample = ioMainBuffer?.assumingMemoryBound(to: Float32.self) else {
|
||||
return noErr
|
||||
}
|
||||
|
||||
return EQMDevice.doIO(
|
||||
clientID: inClientID,
|
||||
operationID: inOperationID,
|
||||
sample: sample,
|
||||
sampleTime: Int(inIOCycleInfo.pointee.mInputTime.mSampleTime),
|
||||
frameSize: inIOBufferFrameSize
|
||||
)
|
||||
}
|
||||
|
||||
func EQM_EndIOOperation (inDriver: AudioServerPlugInDriverRef, inDeviceObjectID: AudioObjectID, inClientID: UInt32, inOperationID: UInt32, inIOBufferFrameSize: UInt32, inIOCycleInfo: UnsafePointer<AudioServerPlugInIOCycleInfo>) -> OSStatus {
|
||||
// This is called at the end of an IO operation. This device doesn't do anything, so just check
|
||||
// the arguments and return.
|
||||
guard EQMDriver.validateDriver(inDriver) else { return kAudioHardwareBadObjectError }
|
||||
return noErr
|
||||
}
|
101
native/driver/Source/EQMObject.swift
Normal file
101
native/driver/Source/EQMObject.swift
Normal file
@ -0,0 +1,101 @@
|
||||
//
|
||||
// EQMObject.swift
|
||||
// eqMac
|
||||
//
|
||||
// Created by Romans Kisils on 21/08/2021.
|
||||
// Copyright © 2021 Bitgapp. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreAudio.AudioServerPlugIn
|
||||
|
||||
protocol EQMObject: class {
|
||||
static func hasProperty (objectID: AudioObjectID?, address: AudioObjectPropertyAddress) -> Bool
|
||||
static func isPropertySettable (objectID: AudioObjectID?, address: AudioObjectPropertyAddress) -> Bool
|
||||
static func getPropertyDataSize (objectID: AudioObjectID?, address: AudioObjectPropertyAddress) -> UInt32?
|
||||
static func getPropertyData (objectID: AudioObjectID?, address: AudioObjectPropertyAddress, inData: UnsafeRawPointer?) -> EQMObjectProperty?
|
||||
static func setPropertyData (objectID: AudioObjectID?, address: AudioObjectPropertyAddress, data: UnsafeRawPointer, changedProperties: inout [AudioObjectPropertyAddress]) -> OSStatus
|
||||
}
|
||||
|
||||
enum EQMObjectProperty {
|
||||
// Primitives
|
||||
case audioClassID(AudioClassID)
|
||||
case bool(CFBoolean)
|
||||
case string(CFString)
|
||||
case integer(UInt32)
|
||||
case float32(Float32)
|
||||
case float64(Float64)
|
||||
case url(CFURL)
|
||||
case scope(AudioObjectPropertyScope)
|
||||
case element(AudioObjectPropertyElement)
|
||||
|
||||
// Structs
|
||||
case streamDescription(AudioStreamBasicDescription)
|
||||
case channelLayout(AudioChannelLayout)
|
||||
case valueRange(AudioValueRange)
|
||||
|
||||
// Arrays
|
||||
case objectIDList(ContiguousArray<AudioObjectID>)
|
||||
case customPropertyInfoList(ContiguousArray<AudioServerPlugInCustomPropertyInfo>)
|
||||
case streamDescriptionList(ContiguousArray<AudioStreamRangedDescription>)
|
||||
case valueRangeList(ContiguousArray<AudioValueRange>)
|
||||
case integerList(ContiguousArray<UInt32>)
|
||||
|
||||
func write(to address: UnsafeMutableRawPointer?, size: UnsafeMutablePointer<UInt32>, requestedSize: UInt32?) {
|
||||
switch self {
|
||||
case .bool(let data): self.write(element: data, address: address, size: size)
|
||||
case .string(let data): self.write(element: data, address: address, size: size)
|
||||
case .float32(let data): self.write(element: data, address: address, size: size)
|
||||
case .float64(let data): self.write(element: data, address: address, size: size)
|
||||
case .url(let data): self.write(element: data, address: address, size: size)
|
||||
case .integer(let data),
|
||||
.scope(let data),
|
||||
.element(let data),
|
||||
.audioClassID(let data): self.write(element: data, address: address, size: size)
|
||||
|
||||
case .streamDescription(let data): self.write(element: data, address: address, size: size)
|
||||
case .channelLayout(let data): self.write(element: data, address: address, size: size)
|
||||
case .valueRange(let data): self.write(element: data, address: address, size: size)
|
||||
|
||||
case .objectIDList(let data): self.write(array: data, address: address, size: size, requestedSize: requestedSize)
|
||||
case .customPropertyInfoList(let data): self.write(array: data, address: address, size: size, requestedSize: requestedSize)
|
||||
case .streamDescriptionList(let data): self.write(array: data, address: address, size: size, requestedSize: requestedSize)
|
||||
case .valueRangeList(let data): self.write(array: data, address: address, size: size, requestedSize: requestedSize)
|
||||
case .integerList(let data): self.write(array: data, address: address, size: size, requestedSize: requestedSize)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func write<T>(element: T, address: UnsafeMutableRawPointer?, size: UnsafeMutablePointer<UInt32>) {
|
||||
// Write data size
|
||||
size.pointee = UInt32(MemoryLayout.size(ofValue: element))
|
||||
// Write data
|
||||
address?.assumingMemoryBound(to: T.self).pointee = element
|
||||
}
|
||||
|
||||
private func write<T: Any>(array arr: ContiguousArray<T>, address: UnsafeMutableRawPointer?, size: UnsafeMutablePointer<UInt32>, requestedSize: UInt32?) {
|
||||
var array = arr
|
||||
let elementSize = sizeof(ContiguousArray<T>.Element.self)
|
||||
|
||||
if requestedSize != nil {
|
||||
let requestedCount = Int(requestedSize! / elementSize)
|
||||
array = ContiguousArray(array[0..<requestedCount])
|
||||
log("Data Count: \(arr.count) - Data Size: \(UInt32(arr.count) * elementSize) - Requested Size: \(requestedSize!) - Resulting Count: \(array.count) - Resulting Size: \(UInt32(array.count) * elementSize)")
|
||||
}
|
||||
|
||||
let totalSize = UInt32(array.count) * elementSize
|
||||
|
||||
// Write data size
|
||||
size.pointee = totalSize
|
||||
|
||||
// Write data
|
||||
guard let address = address, totalSize > 0 else {
|
||||
return
|
||||
}
|
||||
var currentAddress = address.assumingMemoryBound(to: ContiguousArray<T>.Element.self)
|
||||
for element in array {
|
||||
currentAddress.pointee = element
|
||||
currentAddress = currentAddress.advanced(by: 1)
|
||||
}
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
import CoreAudio.AudioServerPlugIn
|
||||
|
||||
class EQMPlugIn: EQMObjectProtocol {
|
||||
class EQMPlugIn: EQMObject {
|
||||
static let id = AudioObjectID(kPlugInBundleId)!
|
||||
|
||||
static func hasProperty (objectID: AudioObjectID? = nil, address: AudioObjectPropertyAddress) -> Bool {
|
||||
@ -74,7 +74,7 @@ class EQMPlugIn: EQMObjectProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
static func getPropertyData (objectID: AudioObjectID? = nil, address: AudioObjectPropertyAddress) -> EQMObjectProperty? {
|
||||
static func getPropertyData (objectID: AudioObjectID? = nil, address: AudioObjectPropertyAddress, inData: UnsafeRawPointer?) -> EQMObjectProperty? {
|
||||
switch address.mSelector {
|
||||
case kAudioObjectPropertyBaseClass:
|
||||
// The base class for kAudioPlugInClassID is kAudioObjectClassID
|
||||
@ -93,7 +93,12 @@ class EQMPlugIn: EQMObjectProtocol {
|
||||
case kAudioPlugInPropertyBoxList:
|
||||
return .objectIDList([kObjectID_Box])
|
||||
case kAudioPlugInPropertyTranslateUIDToBox:
|
||||
return .integer(kObjectID_Box)
|
||||
let uid = inData?.assumingMemoryBound(to: CFString?.self).pointee
|
||||
if (CFStringCompare(uid, kBoxUID as CFString, .init(rawValue: 0)) == CFComparisonResult.compareEqualTo) {
|
||||
return .integer(kObjectID_Box)
|
||||
} else {
|
||||
return .integer(kAudioObjectUnknown)
|
||||
}
|
||||
case kAudioPlugInPropertyDeviceList:
|
||||
return .objectIDList([kObjectID_Device])
|
||||
case kAudioPlugInPropertyTranslateUIDToDevice:
|
||||
@ -102,7 +107,12 @@ class EQMPlugIn: EQMObjectProtocol {
|
||||
// just the one device. Note that it is not an error if the string in the
|
||||
// qualifier doesn't match any devices. In such case, kAudioObjectUnknown is
|
||||
// the object ID to return.
|
||||
return .integer(kObjectID_Device)
|
||||
let uid = inData?.assumingMemoryBound(to: CFString?.self).pointee
|
||||
if (CFStringCompare(uid, kDeviceUID as CFString, .init(rawValue: 0)) == CFComparisonResult.compareEqualTo) {
|
||||
return .integer(kObjectID_Device)
|
||||
} else {
|
||||
return .integer(kAudioObjectUnknown)
|
||||
}
|
||||
case kAudioPlugInPropertyResourceBundle:
|
||||
// The resource bundle is a path relative to the path of the plug-in's bundle.
|
||||
// To specify that the plug-in bundle itself should be used, we just return the
|
||||
@ -119,7 +129,7 @@ class EQMPlugIn: EQMObjectProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
static func setPropertyData(objectID: AudioObjectID? = nil, address: AudioObjectPropertyAddress, data: UnsafeRawPointer) -> OSStatus {
|
||||
static func setPropertyData(objectID: AudioObjectID? = nil, address: AudioObjectPropertyAddress, data: UnsafeRawPointer, changedProperties: inout [AudioObjectPropertyAddress]) -> OSStatus {
|
||||
switch address.mSelector {
|
||||
default: return kAudioHardwareUnknownPropertyError
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
import CoreAudio.AudioServerPlugIn
|
||||
|
||||
class EQMStream: EQMObjectProtocol {
|
||||
class EQMStream: EQMObject {
|
||||
static var inputActive = true
|
||||
static var outputActive = true
|
||||
static var description: AudioStreamBasicDescription {
|
||||
@ -78,7 +78,7 @@ class EQMStream: EQMObjectProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
static func getPropertyData (objectID: AudioObjectID?, address: AudioObjectPropertyAddress) -> EQMObjectProperty? {
|
||||
static func getPropertyData (objectID: AudioObjectID?, address: AudioObjectPropertyAddress, inData: UnsafeRawPointer?) -> EQMObjectProperty? {
|
||||
|
||||
switch address.mSelector {
|
||||
case kAudioObjectPropertyBaseClass:
|
||||
@ -177,7 +177,7 @@ class EQMStream: EQMObjectProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
static func setPropertyData(objectID: AudioObjectID?, address: AudioObjectPropertyAddress, data: UnsafeRawPointer) -> OSStatus {
|
||||
static func setPropertyData(objectID: AudioObjectID?, address: AudioObjectPropertyAddress, data: UnsafeRawPointer, changedProperties: inout [AudioObjectPropertyAddress]) -> OSStatus {
|
||||
switch address.mSelector {
|
||||
case kAudioStreamPropertyIsActive:
|
||||
// Changing the active state of a stream doesn't affect IO or change the structure
|
||||
|
@ -9,124 +9,12 @@
|
||||
import Foundation
|
||||
import CoreAudio.AudioServerPlugIn
|
||||
|
||||
// MARK: - Protocols, Enums
|
||||
|
||||
protocol EQMObjectProtocol {
|
||||
static func hasProperty (objectID: AudioObjectID?, address: AudioObjectPropertyAddress) -> Bool
|
||||
static func isPropertySettable (objectID: AudioObjectID?, address: AudioObjectPropertyAddress) -> Bool
|
||||
static func getPropertyDataSize (objectID: AudioObjectID?, address: AudioObjectPropertyAddress) -> UInt32?
|
||||
static func getPropertyData (objectID: AudioObjectID?, address: AudioObjectPropertyAddress) -> EQMObjectProperty?
|
||||
static func setPropertyData (objectID: AudioObjectID?, address: AudioObjectPropertyAddress, data: UnsafeRawPointer) -> OSStatus
|
||||
}
|
||||
|
||||
enum EQMObjectProperty {
|
||||
// Primitives
|
||||
case audioClassID(AudioClassID)
|
||||
case bool(CFBoolean)
|
||||
case string(CFString)
|
||||
case integer(UInt32)
|
||||
case float32(Float32)
|
||||
case float64(Float64)
|
||||
case url(CFURL)
|
||||
case scope(AudioObjectPropertyScope)
|
||||
case element(AudioObjectPropertyElement)
|
||||
|
||||
// Structs
|
||||
case streamDescription(AudioStreamBasicDescription)
|
||||
case channelLayout(AudioChannelLayout)
|
||||
case valueRange(AudioValueRange)
|
||||
|
||||
// Arrays
|
||||
case objectIDList(ContiguousArray<AudioObjectID>)
|
||||
case customPropertyInfoList(ContiguousArray<AudioServerPlugInCustomPropertyInfo>)
|
||||
case streamDescriptionList(ContiguousArray<AudioStreamRangedDescription>)
|
||||
case valueRangeList(ContiguousArray<AudioValueRange>)
|
||||
case integerList(ContiguousArray<UInt32>)
|
||||
|
||||
func write(to address: UnsafeMutableRawPointer?, size: UnsafeMutablePointer<UInt32>) {
|
||||
switch self {
|
||||
case .bool(let data): self.write(element: data, address: address, size: size)
|
||||
case .string(let data): self.write(element: data, address: address, size: size)
|
||||
case .float32(let data): self.write(element: data, address: address, size: size)
|
||||
case .float64(let data): self.write(element: data, address: address, size: size)
|
||||
case .url(let data): self.write(element: data, address: address, size: size)
|
||||
case .integer(let data),
|
||||
.scope(let data),
|
||||
.element(let data),
|
||||
.audioClassID(let data): self.write(element: data, address: address, size: size)
|
||||
|
||||
case .streamDescription(let data): self.write(element: data, address: address, size: size)
|
||||
case .channelLayout(let data): self.write(element: data, address: address, size: size)
|
||||
case .valueRange(let data): self.write(element: data, address: address, size: size)
|
||||
|
||||
case .objectIDList(let data): self.write(array: data, address: address, size: size)
|
||||
case .customPropertyInfoList(let data): self.write(array: data, address: address, size: size)
|
||||
case .streamDescriptionList(let data): self.write(array: data, address: address, size: size)
|
||||
case .valueRangeList(let data): self.write(array: data, address: address, size: size)
|
||||
case .integerList(let data): self.write(array: data, address: address, size: size)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func write<T>(element: T, address: UnsafeMutableRawPointer?, size: UnsafeMutablePointer<UInt32>) {
|
||||
// Write data size
|
||||
size.pointee = UInt32(MemoryLayout.size(ofValue: element))
|
||||
// Write data
|
||||
address?.assumingMemoryBound(to: T.self).pointee = element
|
||||
}
|
||||
|
||||
private func write<T: Collection>(array: T, address: UnsafeMutableRawPointer?, size: UnsafeMutablePointer<UInt32>) {
|
||||
// Write data size
|
||||
size.pointee = UInt32(array.count) * sizeof(T.Element.self)
|
||||
|
||||
// Write data
|
||||
guard let address = address else {
|
||||
return
|
||||
}
|
||||
var currentAddress = address.assumingMemoryBound(to: T.Element.self)
|
||||
for element in array {
|
||||
currentAddress.pointee = element
|
||||
currentAddress = currentAddress.advanced(by: 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Pure Functions
|
||||
|
||||
func volumeToDecibel (_ volume: Float32) -> Float32 {
|
||||
if (volume <= powf(10.0, kMinVolumeDB / 20.0)) {
|
||||
return kMinVolumeDB
|
||||
} else {
|
||||
return 20.0 * log10f(volume)
|
||||
func log (_ msg: String) {
|
||||
if DEBUG {
|
||||
let message = "coreaudiod: eqMac - \(msg)"
|
||||
Swift.print(message)
|
||||
NSLog(message)
|
||||
}
|
||||
}
|
||||
|
||||
func decibelToVolume (_ decibel: Float32) -> Float32 {
|
||||
if (decibel <= kMinVolumeDB) {
|
||||
return 0.0
|
||||
} else {
|
||||
return powf(10.0, decibel / 20.0)
|
||||
}
|
||||
}
|
||||
|
||||
func volumeToScalar (_ volume: Float32) -> Float32 {
|
||||
let decibel = volumeToDecibel(volume);
|
||||
return (decibel - kMinVolumeDB) / (kMaxVolumeDB - kMinVolumeDB);
|
||||
}
|
||||
|
||||
func scalarToVolume (_ scalar: Float32) -> Float32 {
|
||||
let decibel = scalar * (kMaxVolumeDB - kMinVolumeDB) + kMinVolumeDB
|
||||
return decibelToVolume(decibel)
|
||||
}
|
||||
|
||||
func clamp <T: Comparable> (value val: T, min: T, max: T) -> T {
|
||||
var value = val
|
||||
if value < min {
|
||||
value = min
|
||||
} else if value > min {
|
||||
value = max
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// MARK: - Extensions
|
||||
|
@ -28,9 +28,7 @@
|
||||
<key>CFPlugInTypes</key>
|
||||
<dict>
|
||||
<key>443ABAB8-E7B3-491A-B985-BEB9187030DB</key>
|
||||
<array>
|
||||
<string>7080ba34-76cc-40b2-b2b3-819d28460e7e</string>
|
||||
</array>
|
||||
<string>7080ba34-76cc-40b2-b2b3-819d28460e7e</string>
|
||||
</dict>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2015 - 2021 Bitgapp Ltd.</string>
|
||||
|
47
native/shared/AtomicCounter.swift
Normal file
47
native/shared/AtomicCounter.swift
Normal file
@ -0,0 +1,47 @@
|
||||
//
|
||||
// AtomicCounter.swift
|
||||
// eqMac
|
||||
//
|
||||
// Created by Romans Kisils on 20/08/2021.
|
||||
// Copyright © 2021 Bitgapp. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct AtomicCounter<T: FixedWidthInteger> {
|
||||
private let serialQueue: DispatchQueue
|
||||
|
||||
private var _value: T
|
||||
var value: T {
|
||||
get {
|
||||
return self.serialQueue.sync {
|
||||
self._value
|
||||
}
|
||||
}
|
||||
set {
|
||||
self.serialQueue.sync {
|
||||
self._value = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(_ initialValue: T = 0) {
|
||||
self._value = initialValue
|
||||
let uuid = CFUUIDCreateString(nil, CFUUIDCreate(nil)) as String
|
||||
self.serialQueue = DispatchQueue(label: "eqMac.AtomicCounter.\(uuid)")
|
||||
}
|
||||
|
||||
mutating func increment() {
|
||||
self.serialQueue.sync {
|
||||
guard self._value < T.max else { return }
|
||||
self._value += 1
|
||||
}
|
||||
}
|
||||
|
||||
mutating func decrement() {
|
||||
self.serialQueue.sync {
|
||||
guard self._value > T.min else { return }
|
||||
self._value -= 1
|
||||
}
|
||||
}
|
||||
}
|
@ -7,5 +7,9 @@ extension AudioObjectPropertySelector {
|
||||
static func fromString (_ str: String) -> AudioObjectPropertySelector {
|
||||
return AudioObjectPropertySelector(UInt32.init(from: str.byteArray))
|
||||
}
|
||||
|
||||
var code: String {
|
||||
return String(bytes: withUnsafeBytes(of: self.bigEndian, Array.init), encoding: .utf8)!
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,25 +9,26 @@
|
||||
import Foundation
|
||||
|
||||
extension String {
|
||||
func toJSON() -> Any? {
|
||||
guard let data = self.data(using: .utf8, allowLossyConversion: false) else { return nil }
|
||||
return try? JSONSerialization.jsonObject(with: data, options: .mutableContainers)
|
||||
}
|
||||
|
||||
func toDictionary() -> [String: AnyObject]? {
|
||||
if let data = self.data(using: .utf8) {
|
||||
do {
|
||||
return try JSONSerialization.jsonObject(with: data, options: []) as? [String: AnyObject]
|
||||
} catch {
|
||||
print(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var byteArray: [UInt8] {
|
||||
return Array(self.utf8)
|
||||
func toJSON() -> Any? {
|
||||
guard let data = self.data(using: .utf8, allowLossyConversion: false) else { return nil }
|
||||
return try? JSONSerialization.jsonObject(with: data, options: .mutableContainers)
|
||||
}
|
||||
|
||||
func toDictionary() -> [String: AnyObject]? {
|
||||
if let data = self.data(using: .utf8) {
|
||||
do {
|
||||
return try JSONSerialization.jsonObject(with: data, options: []) as? [String: AnyObject]
|
||||
} catch {
|
||||
print(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var byteArray: [UInt8] {
|
||||
return Array(self.utf8)
|
||||
}
|
||||
|
||||
func trim() -> String {
|
||||
return self.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
|
||||
}
|
||||
@ -36,24 +37,24 @@ extension String {
|
||||
extension String: Error {}
|
||||
|
||||
extension String: LocalizedError {
|
||||
public var errorDescription: String? { return self }
|
||||
public var errorDescription: String? { return self }
|
||||
}
|
||||
|
||||
extension String {
|
||||
func capitalizingFirstLetter() -> String {
|
||||
return prefix(1).uppercased() + dropFirst()
|
||||
}
|
||||
|
||||
var camelCasedString: String {
|
||||
return self.components(separatedBy: " ")
|
||||
.enumerated()
|
||||
.map { (index, component) in
|
||||
var result = component.lowercased()
|
||||
if (index != 0) {
|
||||
result = result.capitalizingFirstLetter()
|
||||
}
|
||||
return result
|
||||
}
|
||||
.joined()
|
||||
}
|
||||
func capitalizingFirstLetter() -> String {
|
||||
return prefix(1).uppercased() + dropFirst()
|
||||
}
|
||||
|
||||
var camelCasedString: String {
|
||||
return self.components(separatedBy: " ")
|
||||
.enumerated()
|
||||
.map { (index, component) in
|
||||
var result = component.lowercased()
|
||||
if (index != 0) {
|
||||
result = result.capitalizingFirstLetter()
|
||||
}
|
||||
return result
|
||||
}
|
||||
.joined()
|
||||
}
|
||||
}
|
||||
|
@ -11,3 +11,13 @@ import Foundation
|
||||
func sizeof <T> (_ type: T) -> UInt32 {
|
||||
return UInt32(MemoryLayout.size(ofValue: type))
|
||||
}
|
||||
|
||||
func clamp <T: Comparable> (value val: T, min: T, max: T) -> T {
|
||||
var value = val
|
||||
if value < min {
|
||||
value = min
|
||||
} else if value > min {
|
||||
value = max
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
37
native/shared/Volume.swift
Normal file
37
native/shared/Volume.swift
Normal file
@ -0,0 +1,37 @@
|
||||
//
|
||||
// Volume.swift
|
||||
// eqMac
|
||||
//
|
||||
// Created by Romans Kisils on 21/08/2021.
|
||||
// Copyright © 2021 Bitgapp. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class Volume {
|
||||
static func toDecibel (_ volume: Float32) -> Float32 {
|
||||
if (volume <= powf(10.0, kMinVolumeDB / 20.0)) {
|
||||
return kMinVolumeDB
|
||||
} else {
|
||||
return 20.0 * log10f(volume)
|
||||
}
|
||||
}
|
||||
|
||||
static func fromDecibel (_ decibel: Float32) -> Float32 {
|
||||
if (decibel <= kMinVolumeDB) {
|
||||
return 0.0
|
||||
} else {
|
||||
return powf(10.0, decibel / 20.0)
|
||||
}
|
||||
}
|
||||
|
||||
static func toScalar (_ volume: Float32) -> Float32 {
|
||||
let decibel = toDecibel(volume);
|
||||
return (decibel - kMinVolumeDB) / (kMaxVolumeDB - kMinVolumeDB);
|
||||
}
|
||||
|
||||
static func fromScalar (_ scalar: Float32) -> Float32 {
|
||||
let decibel = scalar * (kMaxVolumeDB - kMinVolumeDB) + kMinVolumeDB
|
||||
return fromDecibel(decibel)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user