diff --git a/src/AudioToolbox/AudioFile.cs b/src/AudioToolbox/AudioFile.cs index 463981623f..f6f1ad929f 100644 --- a/src/AudioToolbox/AudioFile.cs +++ b/src/AudioToolbox/AudioFile.cs @@ -46,6 +46,8 @@ namespace XamCore.AudioToolbox { AIFF = 0x41494646, // AIFF AIFC = 0x41494643, // AIFC WAVE = 0x57415645, // WAVE + [NoWatch, iOS (11,0), Mac(10,13), TV (11,0)] + RF64 = 0x52463634, // RF64 SoundDesigner2 = 0x53643266, // Sd2f Next = 0x4e655854, // NeXT MP3 = 0x4d504733, // MPG3 @@ -60,6 +62,8 @@ namespace XamCore.AudioToolbox { ThreeGP = 0x33677070, // 3gpp ThreeGP2 = 0x33677032, // 3gp2 AMR = 0x616d7266, // amrf + [NoWatch, iOS (11,0), Mac(10,13), TV (11,0)] + FLAC = 0x666c6163, // flac } public enum AudioFileError {// Implictly cast to OSType in AudioFile.h diff --git a/src/AudioUnit/AudioComponent.cs b/src/AudioUnit/AudioComponent.cs index a398466062..e837757a45 100644 --- a/src/AudioUnit/AudioComponent.cs +++ b/src/AudioUnit/AudioComponent.cs @@ -38,6 +38,177 @@ using XamCore.Foundation; namespace XamCore.AudioUnit { + +#if !COREBUILD + // keys are not constants and had to be found in AudioToolbox.framework/Headers/AudioComponent.h + [NoWatch, NoTV, Mac (10,13), iOS (11,0)] + public partial class ResourceUsageInfo : DictionaryContainer { + static NSString userClientK = new NSString ("iokit.user-client"); + static NSString globalNameK = new NSString ("mach-lookup.global-name"); + static NSString networkClientK = new NSString ("network.client"); + static NSString exceptionK = new NSString ("temporary-exception.files.all.read-write"); + + public ResourceUsageInfo () : base () {} + + public ResourceUsageInfo (NSDictionary dic) : base (dic) {} + + public string[] IOKitUserClient { + get { + var array = GetNativeValue (userClientK); + if (array == null ) + return null; + return NSArray.StringArrayFromHandle (array.Handle); + } + set { + if (value == null) + RemoveValue (userClientK); + else + SetArrayValue (userClientK, value); + } + } + + public string[] MachLookUpGlobalName { + get { + var array = GetNativeValue (globalNameK); + if (array == null) + return null; + return NSArray.StringArrayFromHandle (array.Handle); + } + set { + if (value == null) + RemoveValue (globalNameK); + else + SetArrayValue (globalNameK, value); + } + } + + public bool? NetworkClient { + get { + return GetBoolValue (networkClientK); + } + set { + SetBooleanValue (networkClientK, value); + } + } + + public bool? TemporaryExceptionReadWrite { + get { + return GetBoolValue (exceptionK); + } + set { + SetBooleanValue (exceptionK, value); + } + } + } + + // keys are not constants and had to be found in AudioToolbox.framework/Headers/AudioComponent.h + [NoWatch, NoTV, Mac (10,13), iOS (11,0)] + public partial class AudioComponentInfo : DictionaryContainer { + static NSString typeK = new NSString ("type"); + static NSString subtypeK = new NSString ("subtype"); + static NSString manufacturerK = new NSString ("manufacturer"); + static NSString nameK = new NSString ("name"); + static NSString versionK = new NSString ("version"); + static NSString factoryFunctionK = new NSString ("factoryFunction"); + static NSString sandboxSafeK = new NSString ("sandboxSafe"); + static NSString resourceUsageK = new NSString ("resourceUsage"); + static NSString tagsK = new NSString ("tags"); + + public AudioComponentInfo () : base () {} + + public AudioComponentInfo (NSDictionary dic) : base (dic) {} + + public string Type { + get { + return GetStringValue (typeK); + } + set { + SetStringValue (typeK, value); + } + } + + public string Subtype { + get { + return GetStringValue (subtypeK); + } + set { + SetStringValue (subtypeK, value); + } + } + + public string Manufacturer { + get { + return GetStringValue (manufacturerK); + } + set { + SetStringValue (manufacturerK, value); + } + } + + public string Name { + get { + return GetStringValue (nameK); + } + set { + SetStringValue (nameK, value); + } + } + + public nuint? Version { + get { + return GetNUIntValue (versionK); + } + set { + SetNumberValue (versionK, value); + } + } + + public string FactoryFunction { + get { + return GetStringValue (factoryFunctionK); + } + set { + SetStringValue (factoryFunctionK, value); + } + } + + public bool? SandboxSafe { + get { + return GetBoolValue (sandboxSafeK); + } + set { + SetBooleanValue (sandboxSafeK, value); + } + } + + public ResourceUsageInfo ResourceUsage { + get { + return GetStrongDictionary (resourceUsageK); + } + set { + SetNativeValue (resourceUsageK, value?.Dictionary, true); + } + } + + public string[] Tags { + get { + var array = GetNativeValue (tagsK); + if (array == null) + return null; + return NSArray.StringArrayFromHandle (array.Handle); + } + set { + if (value == null) + RemoveValue (tagsK); + else + SetArrayValue (tagsK, value); + } + } + } + +#endif + + public class AudioComponent : INativeObject { #if !COREBUILD internal IntPtr handle; @@ -224,6 +395,56 @@ namespace XamCore.AudioUnit return new XamCore.AppKit.NSImage (AudioComponentGetIcon (handle)); } #endif + [NoWatch, NoTV, Mac (10,13), iOS (11,0)] + [DllImport (Constants.AudioUnitLibrary)] + static extern int /* OSStatus */ AudioUnitExtensionSetComponentList (IntPtr /* CFString */ extensionIdentifier, /* CFArrayRef */ IntPtr audioComponentInfo); + + [NoWatch, NoTV, Mac (10,13), iOS (11,0)] + [DllImport (Constants.AudioUnitLibrary)] + static extern /* CFArrayRef */ IntPtr AudioUnitExtensionCopyComponentList (IntPtr /* CFString */ extensionIdentifier); + + [NoWatch, NoTV, Mac (10,13), iOS (11,0)] + public AudioComponentInfo[] ComponentList { + get { + using (var cfString = new CFString (Name)) { + var cHandle = AudioUnitExtensionCopyComponentList (cfString.Handle); + if (cHandle == IntPtr.Zero) + return null; + using (var nsArray = Runtime.GetNSObject (cHandle, owns: true)) { + if (nsArray == null) + return null; + // make things easier for developers since we do not know how to have an implicit conversion from NSObject to AudioComponentInfo + var dics = NSArray.FromArray (nsArray); + var result = new AudioComponentInfo [dics.Length]; + for (var i = 0; i < result.Length; i++) { + result [i] = new AudioComponentInfo (dics[i]); + } + return result; + } + } + } + set { + if (value == null) + throw new ArgumentNullException (nameof (value)); + using (var cfString = new CFString (Name)) { + var dics = new NSDictionary [value.Length]; + for (var i = 0; i < value.Length; i++) { + dics [i] = value [i].Dictionary; + } + using (var array = NSArray.FromNSObjects (dics)) { + var result = (AudioConverterError) AudioUnitExtensionSetComponentList (cfString.Handle, array.Handle); + switch (result) { + case AudioConverterError.None: + return; + default: + throw new InvalidOperationException ($"ComponentList could not be set, error {result.ToString ()}"); + + } + } + } + } + } + #endif // !COREBUILD } @@ -241,4 +462,4 @@ namespace XamCore.AudioUnit public static NSString ValidationResult = new NSString ("ValidationResult"); } #endif -} \ No newline at end of file +} diff --git a/src/AudioUnit/AudioUnit.cs b/src/AudioUnit/AudioUnit.cs index ed180cff49..3063bc829c 100644 --- a/src/AudioUnit/AudioUnit.cs +++ b/src/AudioUnit/AudioUnit.cs @@ -63,6 +63,10 @@ namespace XamCore.AudioUnit Initialized = -10849, InvalidOfflineRender = -10848, Unauthorized = -10847, + [iOS (11,0), Mac (10,13, onlyOn64: true), TV (11,0), NoWatch] + MidiOutputBufferFull = -66753, + [iOS (11,0), Mac (10,13, onlyOn64: true), TV (11,0), NoWatch] + ExtensionNotFound = -66744, } public enum AudioComponentStatus { // Implictly cast to OSType @@ -352,6 +356,50 @@ namespace XamCore.AudioUnit System = 0 } + public enum AUParameterEventType : uint + { + Immediate = 1, + Ramped = 2, + } + + [StructLayout (LayoutKind.Sequential)] + public struct AudioUnitParameterEvent + { + public uint Scope; + public uint Element; + public uint Parameter; + public AUParameterEventType EventType; + + [StructLayout (LayoutKind.Explicit)] + public struct EventValuesStruct + { + [StructLayout (LayoutKind.Sequential)] + public struct RampStruct + { + public int StartBufferOffset; + public uint DurationInFrames; + public float StartValue; + public float EndValue; + } + + + [FieldOffset (0)] + public RampStruct Ramp; + + [StructLayout (LayoutKind.Sequential)] + public struct ImmediateStruct + { + public uint BufferOffset; + public float Value; + } + + [FieldOffset (0)] + public ImmediateStruct Immediate; + } + + public EventValuesStruct EventValues; + } + public class AudioUnit : IDisposable, XamCore.ObjCRuntime.INativeObject { internal IntPtr handle; @@ -873,6 +921,11 @@ namespace XamCore.AudioUnit return AudioUnitSetParameter (handle, type, scope, audioUnitElement, value, 0); } + public AudioUnitStatus ScheduleParameter (AudioUnitParameterEvent inParameterEvent, uint inNumParamEvents) + { + return AudioUnitScheduleParameters (handle, inParameterEvent, inNumParamEvents); + } + public void Dispose() { Dispose (true); @@ -1032,6 +1085,9 @@ namespace XamCore.AudioUnit static extern AudioUnitStatus AudioUnitSetParameter (IntPtr inUnit, AudioUnitParameterType inID, AudioUnitScopeType inScope, uint inElement, float inValue, uint inBufferOffsetInFrames); + [DllImport (Constants.AudioUnitLibrary)] + static extern AudioUnitStatus AudioUnitScheduleParameters (IntPtr inUnit, AudioUnitParameterEvent inParameterEvent, uint inNumParamEvents); + #if MONOMAC [DllImport (Constants.AudioUnitLibrary)] static extern int AudioObjectGetPropertyData ( @@ -1954,7 +2010,8 @@ namespace XamCore.AudioUnit Hrtf = 2, SoundField = 3, VectorBasedPanning = 4, - StereoPassThrough = 5 + StereoPassThrough = 5, + HrtfHQ = 6, } public enum AU3DMixerAttenuationCurve : uint diff --git a/src/audiounit.cs b/src/audiounit.cs index d87bb3ab3d..f20150ad52 100644 --- a/src/audiounit.cs +++ b/src/audiounit.cs @@ -8,6 +8,7 @@ using System; using System.ComponentModel; +using System.Runtime.InteropServices; using XamCore.AudioUnit; using XamCore.CoreFoundation; @@ -39,6 +40,8 @@ namespace XamCore.AudioUnit { uint frameCount, nint inputBusNumber, AudioBuffers inputData); delegate void AUScheduleParameterBlock (AUEventSampleTime eventSampleTime, uint rampDurationSampleFrames, ulong parameterAddress, float value); + [iOS (11, 0), Mac (10,13), TV (11,0), NoWatch] + delegate int AUMidiOutputEventBlock (long eventSampleTime, byte cable, nint length, IntPtr midiBytes); delegate void AUImplementorValueObserver (AUParameter param, float value); delegate float AUImplementorValueProvider (AUParameter param); @@ -107,6 +110,10 @@ namespace XamCore.AudioUnit { [NullAllowed, Export ("manufacturerName")] string ManufacturerName { get; } + [iOS (11, 0), Mac (10, 13), TV (11, 0), NoWatch] + [NullAllowed, Export ("audioUnitShortName")] + string ShortName { get; } + [Export ("componentVersion")] uint ComponentVersion { get; } @@ -140,6 +147,18 @@ namespace XamCore.AudioUnit { // [NullAllowed, Export ("musicalContextBlock", ArgumentSemantic.Copy)] // AUHostMusicalContextBlock MusicalContextBlock { get; set; } + [Watch (4, 0), TV (11, 0), Mac (10, 13), iOS (11, 0)] + [Export ("MIDIOutputNames", ArgumentSemantic.Copy)] + string[] MidiOutputNames { get; } + + [Watch (4, 0), TV (11, 0), Mac (10, 13), iOS (11, 0)] + [Export ("providesUserInterface")] + bool ProvidesUserInterface { get; } + + [Watch (4, 0), TV (11, 0), Mac (10, 13), iOS (11, 0)] + [NullAllowed, Export ("MIDIOutputEventBlock", ArgumentSemantic.Copy)] + AUMidiOutputEventBlock MidiOutputEventBlock { get; set; } + [NullAllowed, Export ("transportStateBlock", ArgumentSemantic.Copy)] AUHostTransportStateBlock TransportStateBlock { get; set; } @@ -202,7 +221,7 @@ namespace XamCore.AudioUnit { [NullAllowed, Export ("contextName")] string ContextName { get; set; } - [iOS (10,0), Mac (10,12, onlyOn64 : true), TV (10,0)] + [iOS (10,0), Mac (10,12, onlyOn64 : true), TV (10,0), Watch (4, 0)] [Export ("supportsMPE")] bool SupportsMpe { get; } @@ -229,6 +248,11 @@ namespace XamCore.AudioUnit { [Mac (10,11)][iOS (7,0)] [Notification, Field ("kAudioComponentInstanceInvalidationNotification")] NSString AudioComponentInstanceInvalidationNotification { get; } + + [Watch (4, 0), TV (11, 0), Mac (10, 13), iOS (11, 0)] + [Export ("MIDIOutputBufferSizeHint")] + nint MidiOutputBufferSizeHint { get; set; } + } // kept separate from AUAudioUnit, quote: @@ -238,6 +262,14 @@ namespace XamCore.AudioUnit { [BaseType (typeof (AUAudioUnit))] interface AUAudioUnit_AUAudioInputOutputUnit { + [Mac (10,12), NoTV, NoiOS, NoWatch] + [Export ("deviceID")] + uint GetDeviceId (); + + [Mac (10,12), NoTV, NoiOS, NoWatch] + [Export ("setDeviceID:error:")] + bool SetDeviceId (uint deviceID, out NSError outError); + [Export ("canPerformInput")] bool GetCanPerformInput (); @@ -273,7 +305,20 @@ namespace XamCore.AudioUnit { [NullAllowed, Export ("setOutputProvider:")] void SetOutputProvider (AURenderPullInputBlock provider); -} + + // the following are properties but we cannot have properties in Categories. + [Mac (10, 13), NoWatch, NoiOS, NoTV] + [Export ("deviceInputLatency")] + double GetDeviceInputLatency (); + + [Mac (10, 13), NoWatch, NoiOS, NoTV] + [Export ("deviceOutputLatency")] + double GetDeviceOutputLatency (); + + [Watch (4, 0), TV (11, 0), Mac (10, 13), iOS (11, 0)] + [Export ("running")] + bool IsRunning (); + } [iOS (9,0), Mac(10,11, onlyOn64 : true)] [BaseType (typeof(NSObject))] @@ -315,6 +360,10 @@ namespace XamCore.AudioUnit { [Export ("maximumChannelCount")] uint MaximumChannelCount { get; set; } + + [Watch (4, 0), TV (11, 0), Mac (10, 13), iOS (11, 0)] + [Export ("shouldAllocateBuffer")] + bool ShouldAllocateBuffer { get; set; } } [iOS (9,0), Mac(10,11, onlyOn64 : true)] @@ -437,8 +486,7 @@ namespace XamCore.AudioUnit { [Export ("setValue:originator:atHostTime:eventType:")] void SetValue (float value, IntPtr originator, ulong hostTime, AUParameterAutomationEventType eventType); - [iOS (10,0), Mac (10,12, onlyOn64 : true)] - [TV (10,0)] + [iOS (10,0), Mac (10,12, onlyOn64 : true), Watch (4, 0), TV (10, 0)] [Wrap ("SetValue (value, originator.ObserverToken, hostTime, eventType)")] void SetValue (float value, AUParameterObserverToken originator, ulong hostTime, AUParameterAutomationEventType eventType); } diff --git a/tests/monotouch-test/AudioToolbox/AudioComponentTest.cs b/tests/monotouch-test/AudioToolbox/AudioComponentTest.cs new file mode 100644 index 0000000000..bb4357ad11 --- /dev/null +++ b/tests/monotouch-test/AudioToolbox/AudioComponentTest.cs @@ -0,0 +1,122 @@ +// Copyright 2011 Xamarin Inc. All rights reserved + +#if !__WATCHOS__ +using System; +using System.Drawing; +using System.IO; +using System.Collections.Generic; +#if XAMCORE_2_0 +using Foundation; +using AudioToolbox; +using AudioUnit; +using CoreFoundation; +#else +using MonoTouch.Foundation; +using MonoTouch.MediaPlayer; +using MonoTouch.AudioToolbox; +using MonoTouch.CoreFoundation; +#endif +using NUnit.Framework; + +namespace MonoTouchFixtures.AudioToolbox { + + [TestFixture] + [Preserve (AllMembers = true)] + public class AudioComponentTest { + + [Test] + public void GetSetComponentList () + { + TestRuntime.AssertXcodeVersion (9, 0); + var types = new List { AudioTypeOutput.Generic, AudioTypeOutput.Remote, AudioTypeOutput.VoiceProcessingIO }; + foreach (var t in types) { + var resources = new ResourceUsageInfo (); + resources.IOKitUserClient = new string[] { "CustomUserClient1" }; + resources.MachLookUpGlobalName = new string[] { "MachServiceName1" }; + resources.NetworkClient = false; + resources.TemporaryExceptionReadWrite = false; + + var componentInfo = new AudioComponentInfo (); + componentInfo.Type = t.ToString (); + componentInfo.Subtype = "XMPL"; + componentInfo.Name = "XMPL"; + componentInfo.Version = 1; + componentInfo.ResourceUsage = resources; + var component = AudioComponent.FindComponent (t); + if (component == null) + continue; + var l = component.ComponentList; + Assert.IsNull (l, "List is not null."); + l = new AudioComponentInfo[] { componentInfo }; + //monotouchtests does not have permissions to deal with the hwd. + Assert.Throws (() => component.ComponentList = l); + } + } + + [Test] + public void GetSetNullComponentList () + { + TestRuntime.AssertXcodeVersion (9, 0); + var types = new List { AudioTypeOutput.Generic, AudioTypeOutput.Remote, AudioTypeOutput.VoiceProcessingIO }; + foreach (var t in types) { + var resources = new ResourceUsageInfo (); + resources.IOKitUserClient = new string[] { "CustomUserClient1" }; + resources.MachLookUpGlobalName = new string[] { "MachServiceName1" }; + resources.NetworkClient = false; + resources.TemporaryExceptionReadWrite = false; + + var componentInfo = new AudioComponentInfo (); + componentInfo.Type = t.ToString (); + componentInfo.Subtype = "XMPL"; + componentInfo.Name = "XMPL"; + componentInfo.Version = 1; + componentInfo.ResourceUsage = resources; + var component = AudioComponent.FindComponent (t); + if (component == null) + continue; + //monotouchtests does not have permissions to deal with the hwd. + Assert.Throws (() => component.ComponentList = null); + } + } + + // test the diff properties of the ResourceUsageInfo since it was manually done + + [Test] + public void TestResourceUsageInfoIOKitUserClient () + { + TestRuntime.AssertXcodeVersion (9, 0); + var clientId = "CustomUserClient1"; + var resources = new ResourceUsageInfo (); + resources.IOKitUserClient = new string[] { clientId }; + var userClientList = resources.IOKitUserClient; + Assert.IsNotNull (userClientList); + Assert.AreEqual (1, userClientList.Length, "List does not have all client ids."); + Assert.AreEqual (clientId, userClientList [0], "Client ids are not the same."); + + // similar test but with null values. + + resources.IOKitUserClient = null; + Assert.IsNull (resources.IOKitUserClient, "Value was not set to null."); + } + + [Test] + public void TestResourceUsageInfoMachLookUpGlobalName () + { + TestRuntime.AssertXcodeVersion (9, 0); + var serviceName = "MachServiceName1"; + var resources = new ResourceUsageInfo (); + resources.MachLookUpGlobalName = new string[] { serviceName }; + var serviceNames = resources.MachLookUpGlobalName; + Assert.NotNull (serviceNames, "Returned list is null"); + Assert.AreEqual (1, serviceNames.Length, "List does not have all service names."); + Assert.AreEqual (serviceName, serviceNames [0], "Service names are not equal."); + + // similar test but with null values + + resources.MachLookUpGlobalName = null; + Assert.IsNull (resources.MachLookUpGlobalName, "Value was no set to null."); + } + } +} + +#endif // !__WATCHOS__ \ No newline at end of file diff --git a/tests/monotouch-test/monotouch-test.csproj b/tests/monotouch-test/monotouch-test.csproj index 8f2530adaa..1e8698c2b8 100644 --- a/tests/monotouch-test/monotouch-test.csproj +++ b/tests/monotouch-test/monotouch-test.csproj @@ -1,4 +1,4 @@ - + Debug @@ -658,6 +658,7 @@ +