diff --git a/LXST/Platforms/darwin/coreaudioconstants.py b/LXST/Platforms/darwin/coreaudioconstants.py new file mode 100644 index 0000000..5cdef04 --- /dev/null +++ b/LXST/Platforms/darwin/coreaudioconstants.py @@ -0,0 +1,185 @@ +# Adapted from Bastian Bechtold's soundcard library, originally released +# under the BSD 3-Clause License +# +# https://github.com/bastibe/SoundCard +# +# Copyright (c) 2016 Bastian Bechtold +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the +# distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# Modifications and improvements Copyright 2025 Mark Qvist, and released +# under the same BSD 3-Clause License. + +kAudioObjectSystemObject = 1 +kAudioHardwarePropertyDevices = int.from_bytes(b'dev#', byteorder='big') +kAudioHardwarePropertyDefaultInputDevice = int.from_bytes(b'dIn ', byteorder='big') +kAudioHardwarePropertyDefaultOutputDevice = int.from_bytes(b'dOut', byteorder='big') + +kAudioObjectPropertyScopeGlobal = int.from_bytes(b'glob', byteorder='big') +kAudioObjectPropertyScopeInput = int.from_bytes(b'inpt', byteorder='big') +kAudioObjectPropertyScopeOutput = int.from_bytes(b'outp', byteorder='big') +kAudioObjectPropertyScopePlayThrough = int.from_bytes(b'ptru', byteorder='big') + +kAudioObjectPropertyName = int.from_bytes(b'lnam', byteorder='big') +kAudioObjectPropertyModelName = int.from_bytes(b'lmod', byteorder='big') +kAudioObjectPropertyManufacturer = int.from_bytes(b'lmak', byteorder='big') + +kAudioDevicePropertyNominalSampleRate = int.from_bytes(b'nsrt', byteorder='big') +kAudioDevicePropertyBufferFrameSize = int.from_bytes(b'fsiz', byteorder='big') +kAudioDevicePropertyBufferFrameSizeRange = int.from_bytes(b'fsz#', byteorder='big') +kAudioDevicePropertyUsesVariableBufferFrameSizes = int.from_bytes(b'vfsz', byteorder='big') +kAudioDevicePropertyStreamConfiguration = int.from_bytes(b'slay', byteorder='big') + +kCFStringEncodingUTF8 = 0x08000100 +kAudioObjectPropertyElementMaster = 0 + +kAudioUnitType_Output = int.from_bytes(b'auou', byteorder='big') +kAudioUnitManufacturer_Apple = int.from_bytes(b'appl', byteorder='big') +kAudioUnitSubType_GenericOutput = int.from_bytes(b'genr', byteorder='big') +kAudioUnitSubType_HALOutput = int.from_bytes(b'ahal', byteorder='big') +kAudioUnitSubType_DefaultOutput = int.from_bytes(b'def ', byteorder='big') +# The audio unit can do input from the device as well as output to the +# device. Bus 0 is used for the output side, bus 1 is used to get audio +# input from the device. +outputbus = 0 +inputbus = 1 + +def error_number_to_string(num): + if num == kAudioUnitErr_InvalidProperty: + return "The property is not supported" + elif num == kAudioUnitErr_InvalidParameter: + return "The parameter is not supported" + elif num == kAudioUnitErr_InvalidElement: + return "The specified element is not valid" + elif num == kAudioUnitErr_NoConnection: + return "There is no connection (generally an audio unit is asked to render but it has" \ + " not input from which to gather data)" + elif num == kAudioUnitErr_FailedInitialization: + return "The audio unit is unable to be initialized" + elif num == kAudioUnitErr_TooManyFramesToProcess: + return "When an audio unit is initialized it has a value which specifies the max" \ + " number of frames it will be asked to render at any given time. If an audio" \ + " unit is asked to render more than this, this error is returned." + elif num == kAudioUnitErr_InvalidFile: + return "If an audio unit uses external files as a data source, this error is returned" \ + " if a file is invalid (Apple's DLS synth returns this error)" + elif num == kAudioUnitErr_UnknownFileType: + return "If an audio unit uses external files as a data source, this error is returned" \ + " if a file is invalid (Apple's DLS synth returns this error)" + elif num == kAudioUnitErr_FileNotSpecified: + return "If an audio unit uses external files as a data source, this error is returned" \ + " if a file hasn't been set on it (Apple's DLS synth returns this error)" + elif num == kAudioUnitErr_FormatNotSupported: + return "Returned if an input or output format is not supported" + elif num == kAudioUnitErr_Uninitialized: + return "Returned if an operation requires an audio unit to be initialized and it is not." + elif num == kAudioUnitErr_InvalidScope: + return "The specified scope is invalid" + elif num == kAudioUnitErr_PropertyNotWritable: + return "The property cannot be written" + elif num == kAudioUnitErr_CannotDoInCurrentContext: + return "Returned when an audio unit is in a state where it can't perform the requested" \ + " action now - but it could later. Its usually used to guard a render operation" \ + " when a reconfiguration of its internal state is being performed." + elif num == kAudioUnitErr_InvalidPropertyValue: + return "The property is valid, but the value of the property being provided is not" + elif num == kAudioUnitErr_PropertyNotInUse: + return "Returned when a property is valid, but it hasn't been set to a valid value at this time." + elif num == kAudioUnitErr_Initialized: + return "Indicates the operation cannot be performed because the audio unit is initialized." + elif num == kAudioUnitErr_InvalidOfflineRender: + return "Used to indicate that the offline render operation is invalid. For instance," \ + " when the audio unit needs to be pre-flighted, but it hasn't been." + elif num == kAudioUnitErr_Unauthorized: + return "Returned by either Open or Initialize, this error is used to indicate that the" \ + " audio unit is not authorised, that it cannot be used. A host can then present" \ + " a UI to notify the user the audio unit is not able to be used in its current state." + elif num == kAudioComponentErr_InstanceInvalidated: + return "the component instance's implementation is not available, most likely because the process" \ + " that published it is no longer running" + else: + return "error number {}".format(num) + +kAudioUnitErr_InvalidProperty = -10879 +kAudioUnitErr_InvalidParameter = -10878 +kAudioUnitErr_InvalidElement = -10877 +kAudioUnitErr_NoConnection = -10876 +kAudioUnitErr_FailedInitialization = -10875 +kAudioUnitErr_TooManyFramesToProcess = -10874 +kAudioUnitErr_InvalidFile = -10871 +kAudioUnitErr_UnknownFileType = -10870 +kAudioUnitErr_FileNotSpecified = -10869 +kAudioUnitErr_FormatNotSupported = -10868 +kAudioUnitErr_Uninitialized = -10867 +kAudioUnitErr_InvalidScope = -10866 +kAudioUnitErr_PropertyNotWritable = -10865 +kAudioUnitErr_CannotDoInCurrentContext = -10863 +kAudioUnitErr_InvalidPropertyValue = -10851 +kAudioUnitErr_PropertyNotInUse = -10850 +kAudioUnitErr_Initialized = -10849 +kAudioUnitErr_InvalidOfflineRender = -10848 +kAudioUnitErr_Unauthorized = -10847 +kAudioComponentErr_InstanceInvalidated = -66749 +kAudioUnitErr_RenderTimeout = -66745 + +kAudioOutputUnitProperty_CurrentDevice = 2000 +kAudioOutputUnitProperty_EnableIO = 2003 # scope output, element 0 == output, +kAudioOutputUnitProperty_HasIO = 2006 # scope input, element 1 == input +kAudioOutputUnitProperty_IsRunning = 2001 +kAudioOutputUnitProperty_ChannelMap = 2002 + +kAudioFormatLinearPCM = int.from_bytes(b'lpcm', byteorder='big') +kAudioFormatFlagIsFloat = 0x1 + +kAudioUnitProperty_StreamFormat = 8 +kAudioUnitProperty_CPULoad = 6 +kAudioUnitProperty_Latency = 12 +kAudioUnitProperty_SupportedNumChannels = 13 +kAudioUnitProperty_MaximumFramesPerSlice = 14 +kAudioUnitProperty_SetRenderCallback = 23 +kAudioOutputUnitProperty_SetInputCallback = 2005 +kAudioUnitProperty_StreamFormat = 8 +kAudioUnitProperty_SampleRate = 2 +kAudioUnitProperty_ContextName = 25 +kAudioUnitProperty_ElementName = 30 +kAudioUnitProperty_NickName = 54 + +kAudioUnitRenderAction_PreRender = 1 << 2 +kAudioUnitRenderAction_PostRender = 1 << 3 +kAudioUnitRenderAction_OutputIsSilence = 1 << 4 +kAudioOfflineUnitRenderAction_Preflight = 1 << 5 +kAudioOfflineUnitRenderAction_Render = 1 << 6 +kAudioOfflineUnitRenderAction_Complete = 1 << 7 +kAudioUnitRenderAction_PostRenderError = 1 << 8 +kAudioUnitRenderAction_DoNotCheckRenderArgs = 1 << 9 + +kAudioUnitScope_Global = 0 # The context for audio unit characteristics that apply to the audio unit as a whole +kAudioUnitScope_Input = 1 # The context for audio data coming into an audio unit +kAudioUnitScope_Output = 2 # The context for audio data leaving an audio unit \ No newline at end of file diff --git a/LXST/Platforms/darwin/soundcard.py b/LXST/Platforms/darwin/soundcard.py index a9ed662..100ae10 100644 --- a/LXST/Platforms/darwin/soundcard.py +++ b/LXST/Platforms/darwin/soundcard.py @@ -55,7 +55,7 @@ with open(os.path.join(_package_dir, 'coreaudio.h'), 'rt') as f: _ca = _ffi.dlopen('CoreAudio') _au = _ffi.dlopen('AudioUnit') -from soundcard import coreaudioconstants as _cac +import LXST.Platforms.darwin.coreaudioconstants as _cac def all_speakers(): diff --git a/LXST/Primitives/Telephony.py b/LXST/Primitives/Telephony.py index e0aceae..0bf762f 100644 --- a/LXST/Primitives/Telephony.py +++ b/LXST/Primitives/Telephony.py @@ -581,11 +581,12 @@ class Telephone(SignallingReceiver): self.audio_input.stop() self.transmit_mixer.stop() self.transmit_pipeline.stop() - self.transmit_mixer = Mixer(target_frame_ms=self.target_frame_time_ms, gain=self.transmit_gain) - self.audio_input = LineSource(preferred_device=self.microphone_device, target_frame_ms=self.target_frame_time_ms, codec=Raw(), sink=self.transmit_mixer, filters=self.active_call.filters) - self.transmit_pipeline = Pipeline(source=self.transmit_mixer, - codec=self.transmit_codec, - sink=self.active_call.packetizer) + self.transmit_mixer = Mixer(target_frame_ms=self.target_frame_time_ms, gain=self.transmit_gain) + + self.audio_input = LineSource(preferred_device=self.microphone_device, target_frame_ms=self.target_frame_time_ms, codec=Raw(), + sink=self.transmit_mixer, filters=self.active_call.filters, skip=0.075, ease_in=0.0) + + self.transmit_pipeline = Pipeline(source=self.transmit_mixer, codec=self.transmit_codec, sink=self.active_call.packetizer) self.transmit_mixer.mute(self.__transmit_muted) self.transmit_mixer.start() @@ -609,11 +610,13 @@ class Telephone(SignallingReceiver): self.__prepare_dialling_pipelines() self.active_call.packetizer = Packetizer(self.active_call, failure_callback=self.__packetizer_failure) - self.transmit_mixer = Mixer(target_frame_ms=self.target_frame_time_ms, gain=self.transmit_gain) - self.audio_input = LineSource(preferred_device=self.microphone_device, target_frame_ms=self.target_frame_time_ms, codec=Raw(), sink=self.transmit_mixer, filters=self.active_call.filters) - self.transmit_pipeline = Pipeline(source=self.transmit_mixer, - codec=self.transmit_codec, - sink=self.active_call.packetizer) + + self.transmit_mixer = Mixer(target_frame_ms=self.target_frame_time_ms, gain=self.transmit_gain) + + self.audio_input = LineSource(preferred_device=self.microphone_device, target_frame_ms=self.target_frame_time_ms, codec=Raw(), + sink=self.transmit_mixer, filters=self.active_call.filters, skip=0.075, ease_in=0.225) + + self.transmit_pipeline = Pipeline(source=self.transmit_mixer, codec=self.transmit_codec, sink=self.active_call.packetizer) self.active_call.audio_source = LinkSource(link=self.active_call, signalling_receiver=self, sink=self.receive_mixer) self.receive_mixer.set_source_max_frames(self.active_call.audio_source, 2) diff --git a/LXST/_version.py b/LXST/_version.py index cd1ee63..98a433b 100644 --- a/LXST/_version.py +++ b/LXST/_version.py @@ -1 +1 @@ -__version__ = "0.4.4" +__version__ = "0.4.5"