text: // license:BSD-3-Clause
  // copyright-holders:Vas Crabb
  //============================================================
  //
  //  sound.c - CoreAudio implementation of MAME sound routines
  //
  //============================================================
  
  #include "sound_module.h"
  #include "modules/osdmodule.h"
  #include "modules/lib/osdobj_common.h"
  
  #ifdef SDLMAME_MACOSX
  
  #include <AvailabilityMacros.h>
  #include <AudioToolbox/AudioToolbox.h>
  #include <AudioUnit/AudioUnit.h>
  #include <CoreAudio/CoreAudio.h>
  #include <CoreFoundation/CoreFoundation.h>
  #include <CoreServices/CoreServices.h>
  
  #include <string.h>
  
  
  #ifdef MAC_OS_X_VERSION_MAX_ALLOWED
  
  #if MAC_OS_X_VERSION_MAX_ALLOWED < 1060
  
  typedef ComponentDescription AudioComponentDescription;
  
  #endif // MAC_OS_X_VERSION_MAX_ALLOWED < 1060
  
  #endif // MAC_OS_X_VERSION_MAX_ALLOWED
  
  
  class sound_coreaudio : public osd_module, public sound_module
  {
  public:
  	sound_coreaudio() :
  		osd_module(OSD_SOUND_PROVIDER, "coreaudio"),
  		sound_module(),
  		m_graph(nullptr),
  		m_node_count(0),
  		m_sample_bytes(0),
  		m_headroom(0),
  		m_buffer_size(0),
  		m_buffer(nullptr),
  		m_playpos(0),
  		m_writepos(0),
  		m_in_underrun(false),
  		m_scale(128),
  		m_overflows(0),
  		m_underflows(0)
  	{
  	}
  	virtual ~sound_coreaudio()
  	{
  	}
  
  	virtual int init(osd_options const &options) override;
  	virtual void exit() override;
  
  	// sound_module
  
  	virtual void update_audio_stream(bool is_throttled, int16_t const *buffer, int samples_this_frame) override;
  	virtual void set_mastervolume(int attenuation) override;
  
  private:
  	struct node_detail
  	{
  		node_detail() : m_node(0), m_unit(nullptr) { }
  
  		AUNode      m_node;
  		AudioUnit   m_unit;
  	};
  
  	enum
  	{
  		LATENCY_MIN = 1,
  		LATENCY_MAX = 5,
  		EFFECT_COUNT_MAX = 10
  	};
  
  	uint32_t clamped_latency() const { return unsigned(std::max(std::min(m_audio_latency, int(LATENCY_MAX)), int(LATENCY_MIN))); }
  	uint32_t buffer_avail() const { return ((m_writepos >= m_playpos) ? m_buffer_size : 0) + m_playpos - m_writepos; }
  	uint32_t buffer_used() const { return ((m_playpos > m_writepos) ? m_buffer_size : 0) + m_writepos - m_playpos; }
  
  	void copy_scaled(void *dst, void const *src, uint32_t bytes) const
  	{
  		bytes /= sizeof(int16_t);
  		int16_t const *s = (int16_t const *)src;
  		for (int16_t *d = (int16_t *)dst; bytes > 0; bytes--, s++, d++)
  			*d = (*s * m_scale) >> 7;
  	}
  
  	bool create_graph(osd_options const &options);
  	bool add_output(char const *name);
  	bool add_device_output(char const *name);
  	bool add_converter();
  	bool add_effect(char const *name);
  
  	OSStatus add_node(OSType type, OSType subtype, OSType manufacturer)
  	{
  		AudioComponentDescription const desc = { type, subtype, manufacturer, 0, 0 };
  		return AUGraphAddNode(m_graph, &desc, &m_node_details[m_node_count].m_node);
  	}
  
  	OSStatus get_next_node_info()
  	{
  		return AUGraphNodeInfo(
  				m_graph,
  				m_node_details[m_node_count].m_node,
  				nullptr,
  				&m_node_details[m_node_count].m_unit);
  	}
  
  	OSStatus connect_next_node()
  	{
  		return AUGraphConnectNodeInput(
  				m_graph,
  				m_node_details[m_node_count].m_node,
  				0,
  				m_node_details[m_node_count - 1].m_node,
  				0);
  	}
  
  	bool get_output_device_id(char const *name, AudioDeviceID &id) const;
  	char *get_device_uid(AudioDeviceID id) const;
  	char *get_device_name(AudioDeviceID id) const;
  	UInt32 get_output_stream_count(
  			AudioDeviceID id,
  			char const *uid,
  			char const *name) const;
  
  	bool extract_effect_info(
  			char const          *name,
  			CFPropertyListRef   properties,
  			OSType              &type,
  			OSType              &subtype,
  			OSType              &manufacturer,
  			CFPropertyListRef   &class_info) const;
  	CFPropertyListRef load_property_list(char const *name) const;
  
  	char *convert_cfstring_to_utf8(CFStringRef str) const
  	{
  		CFIndex const len = CFStringGetMaximumSizeForEncoding(
  				CFStringGetLength(str),
  				kCFStringEncodingUTF8);
  		char *const result = global_alloc_array_clear<char>(len + 1);
  		if (!CFStringGetCString(str, result, len + 1, kCFStringEncodingUTF8))
  		{
  			global_free_array(result);
  			return nullptr;
  		}
  		return result;
  	}
  
  	OSStatus render(
  			AudioUnitRenderActionFlags  *action_flags,
  			const AudioTimeStamp        *timestamp,
  			UInt32                      bus_number,
  			UInt32                      number_frames,
  			AudioBufferList             *data);
  
  	static OSStatus render_callback(
  			void                        *refcon,
  			AudioUnitRenderActionFlags  *action_flags,
  			const AudioTimeStamp        *timestamp,
  			UInt32                      bus_number,
  			UInt32                      number_frames,
  			AudioBufferList             *data);
  
  	AUGraph     m_graph;
  	unsigned    m_node_count;
  	node_detail m_node_details[EFFECT_COUNT_MAX + 2];
  
  	uint32_t      m_sample_bytes;
  	uint32_t      m_headroom;
  	uint32_t      m_buffer_size;
  	int8_t        *m_buffer;
  	uint32_t      m_playpos;
  	uint32_t      m_writepos;
  	bool        m_in_underrun;
  	int32_t       m_scale;
  	unsigned    m_overflows;
  	unsigned    m_underflows;
  };
  
  
  int sound_coreaudio::init(const osd_options &options)
  {
  	OSStatus err;
  
  	// Don't bother with any of this if sound is disabled
  	if (sample_rate() == 0)
  		return 0;
  
  	// Create the output graph
  	osd_printf_verbose("Audio: Start initialization\n");
  	if (!create_graph(options))
  		return -1;
  
  	// Set audio stream format for two-channel native-endian 16-bit packed linear PCM
  	AudioStreamBasicDescription format;
  	format.mSampleRate          = sample_rate();
  	format.mFormatID            = kAudioFormatLinearPCM;
  	format.mFormatFlags         = kAudioFormatFlagsNativeEndian
  								| kLinearPCMFormatFlagIsSignedInteger
  								| kLinearPCMFormatFlagIsPacked;
  	format.mFramesPerPacket     = 1;
  	format.mChannelsPerFrame    = 2;
  	format.mBitsPerChannel      = 16;
  	format.mBytesPerFrame       = format.mChannelsPerFrame * format.mBitsPerChannel / 8;
  	format.mBytesPerPacket      = format.mFramesPerPacket * format.mBytesPerFrame;
  	err = AudioUnitSetProperty(
  			m_node_details[m_node_count - 1].m_unit,
  			kAudioUnitProperty_StreamFormat,
  			kAudioUnitScope_Input,
  			0,
  			&format,
  			sizeof(format));
  	if (noErr != err)
  	{
  		osd_printf_error("Could not set audio output stream format (%ld)\n", (long)err);
  		goto close_graph_and_return_error;
  	}
  	m_sample_bytes = format.mBytesPerFrame;
  
  	// Allocate buffer
  	m_headroom = m_sample_bytes * (clamped_latency() * sample_rate() / 40);
  	m_buffer_size = m_sample_bytes * std::max<uint32_t>(sample_rate() * (clamped_latency() + 3) / 40, 256U);
  	m_buffer = global_alloc_array_clear<int8_t>(m_buffer_size);
  	if (!m_buffer)
  	{
  		osd_printf_error("Could not allocate stream buffer\n");
  		goto close_graph_and_return_error;
  	}
  	m_playpos = 0;
  	m_writepos = m_headroom;
  	m_in_underrun = false;
  	m_scale = 128;
  	m_overflows = m_underflows = 0;
  
  	// Initialise and start
  	err = AUGraphInitialize(m_graph);
  	if (noErr != err)
  	{
  		osd_printf_error("Could not initialize AudioUnit graph (%ld)\n", (long)err);
  		goto free_buffer_and_return_error;
  	}
  	err = AUGraphStart(m_graph);
  	if (noErr != err)
  	{
  		osd_printf_error("Could not start AudioUnit graph (%ld)\n", (long)err);
  		AUGraphUninitialize(m_graph);
  		goto free_buffer_and_return_error;
  	}
  	osd_printf_verbose("Audio: End initialization\n");
  	return 0;
  
  free_buffer_and_return_error:
  	global_free_array(m_buffer);
  	m_buffer_size = 0;
  	m_buffer = nullptr;
  close_graph_and_return_error:
  	AUGraphClose(m_graph);
  	DisposeAUGraph(m_graph);
  	m_graph = nullptr;
  	m_node_count = 0;
  	return -1;
  }
  
  
  void sound_coreaudio::exit()
  {
  	osd_printf_verbose("Audio: Start deinitialization\n");
  	if (m_graph)
  	{
  		osd_printf_verbose("Stopping CoreAudio output\n");
  		AUGraphStop(m_graph);
  		AUGraphUninitialize(m_graph);
  		DisposeAUGraph(m_graph);
  		m_graph = nullptr;
  		m_node_count = 0;
  	}
  	if (m_buffer)
  	{
  		global_free_array(m_buffer);
  		m_buffer = nullptr;
  	}
  	if (m_overflows || m_underflows)
  		osd_printf_verbose("Sound buffer: overflows=%u underflows=%u\n", m_overflows, m_underflows);
  	osd_printf_verbose("Audio: End deinitialization\n");
  }
  
  
  void sound_coreaudio::update_audio_stream(bool is_throttled, int16_t const *buffer, int samples_this_frame)
  {
  	if ((sample_rate() == 0) || !m_buffer)
  		return;
  
  	uint32_t const bytes_this_frame = samples_this_frame * m_sample_bytes;
  	if (bytes_this_frame >= buffer_avail())
  	{
  		m_overflows++;
  		return;
  	}
  
  	uint32_t const chunk = std::min(m_buffer_size - m_writepos, bytes_this_frame);
  	memcpy(m_buffer + m_writepos, (int8_t *)buffer, chunk);
  	m_writepos += chunk;
  	if (m_writepos >= m_buffer_size)
  		m_writepos = 0;
  
  	if (chunk < bytes_this_frame)
  	{
  		assert(0U == m_writepos);
  		assert(m_playpos > (bytes_this_frame - chunk));
  		memcpy(m_buffer, (int8_t *)buffer + chunk, bytes_this_frame - chunk);
  		m_writepos += bytes_this_frame - chunk;
  	}
  }
  
  
  void sound_coreaudio::set_mastervolume(int attenuation)
  {
  	int const clamped_attenuation = std::max(std::min(attenuation, 0), -32);
  	m_scale = (-32 == clamped_attenuation) ? 0 : (int32_t)(pow(10.0, clamped_attenuation / 20.0) * 128);
  }
  
  
  bool sound_coreaudio::create_graph(osd_options const &options)
  {
  	OSStatus err;
  
  	osd_printf_verbose("Creating AudioUnit graph\n");
  	if (noErr != (err = NewAUGraph(&m_graph)))
  	{
  		osd_printf_error("Failed to create AudioUnit graph (%ld)\n", (long)err);
  		goto return_error;
  	}
  	if (noErr != (err = AUGraphOpen(m_graph)))
  	{
  		osd_printf_error("Failed to open AudioUnit graph (%ld)\n", (long)err);
  		goto dispose_graph_and_return_error;
  	}
  
  	if (!add_output(options.audio_output()))
  		goto close_graph_and_return_error;
  
  	for (unsigned i = EFFECT_COUNT_MAX; 0U < i; i--)
  	{
  		if (!add_effect(options.audio_effect(i - 1)))
  			goto close_graph_and_return_error;
  	}
  
  	if ((1U < m_node_count) && !add_converter())
  		goto close_graph_and_return_error;
  
  	{
  		AURenderCallbackStruct const renderer = { sound_coreaudio::render_callback, this };
  		err = AUGraphSetNodeInputCallback(
  				m_graph,
  				m_node_details[m_node_count - 1].m_node,
  				0,
  				&renderer);
  	}
  	if (noErr != err)
  	{
  		osd_printf_error(
  				"Failed to set audio render callback for AudioUnit graph (%ld)\n",
  				(long)err);
  		goto close_graph_and_return_error;
  	}
  
  	err = AUGraphUpdate(m_graph, nullptr);
  	if (noErr != err)
  	{
  		osd_printf_error(
  				"Failed to update AudioUnit graph (%ld)\n",
  				(long)err);
  		goto close_graph_and_return_error;
  	}
  
  	return true;
  
  close_graph_and_return_error:
  	AUGraphClose(m_graph);
  dispose_graph_and_return_error:
  	DisposeAUGraph(m_graph);
  return_error:
  	m_graph = nullptr;
  	m_node_count = 0;
  	return false;
  }
  
  
  bool sound_coreaudio::add_output(char const *name)
  {
  	OSStatus err;
  
  	if (*name && strcmp(name, OSDOPTVAL_AUTO) && !add_device_output(name))
  		return false;
  
  	if (0U == m_node_count)
  	{
  		osd_printf_verbose("Adding default output to AudioUnit graph\n");
  		err = add_node(
  				kAudioUnitType_Output,
  				kAudioUnitSubType_DefaultOutput,
  				kAudioUnitManufacturer_Apple);
  		if (noErr != err)
  		{
  			osd_printf_error(
  					"Failed to add default sound output to AudioUnit graph (%ld)\n",
  					(long)err);
  			return false;
  		}
  		if (noErr != (err = get_next_node_info()))
  		{
  			osd_printf_error(
  					"Failed to obtain AudioUnit for default sound output (%ld)\n",
  					(long)err);
  			return false;
  		}
  		m_node_count++;
  	}
  
  	return true;
  }
  
  
  bool sound_coreaudio::add_device_output(char const *name)
  {
  	OSStatus err;
  
  	AudioDeviceID id;
  	if (!get_output_device_id(name, id))
  	{
  		osd_printf_warning(
  				"No audio output device matched %s - falling back to default output\n",
  				name);
  		return true;
  	}
  
  	osd_printf_verbose("Adding HAL output to AudioUnit graph\n");
  	err = add_node(
  			kAudioUnitType_Output,
  			kAudioUnitSubType_HALOutput,
  			kAudioUnitManufacturer_Apple);
  	if (noErr != err)
  	{
  		osd_printf_error(
  				"Failed to add HAL output to AudioUnit graph (%ld) - falling back to default output\n",
  				(long)err);
  		return true;
  	}
  	if (noErr != (err = get_next_node_info()))
  	{
  		osd_printf_error(
  				"Failed to obtain AudioUnit for HAL output (%ld)\n",
  				(long)err);
  		goto remove_node_and_return_error;
  	}
  	err = AudioUnitSetProperty(
  			m_node_details[m_node_count].m_unit,
  			kAudioOutputUnitProperty_CurrentDevice,
  			kAudioUnitScope_Global,
  			0,
  			&id,
  			sizeof(id));
  	if (noErr != (err = get_next_node_info()))
  	{
  		osd_printf_error(
  				"Failed to set HAL output device to %s (%ld)\n",
  				name,
  				(long)err);
  		goto remove_node_and_return_error;
  	}
  	m_node_count++;
  	return true;
  
  remove_node_and_return_error:
  	osd_printf_verbose("Removing failed HAL output from AudioUnit graph\n");
  	err = AUGraphRemoveNode(m_graph, m_node_details[m_node_count].m_node);
  	if (noErr != err)
  	{
  		osd_printf_error(
  				"Failed to remove HAL output from AudioUnit graph (%ld)\n",
  				(long)err);
  		return false;
  	}
  	osd_printf_warning("Falling back to default output");
  	return true;
  }
  
  
  bool sound_coreaudio::add_converter()
  {
  	OSStatus err;
  	osd_printf_verbose("Adding format converter to AudioUnit graph\n");
  	err = add_node(
  			kAudioUnitType_FormatConverter,
  			kAudioUnitSubType_AUConverter,
  			kAudioUnitManufacturer_Apple);
  	if (noErr != err)
  	{
  		osd_printf_error(
  				"Failed to add sound format converter to AudioUnit graph (%ld)\n",
  				(long)err);
  		return false;
  	}
  	if (noErr != (err = get_next_node_info()))
  	{
  		osd_printf_error(
  				"Failed to obtain AudioUnit for sound format converter (%ld)\n",
  				(long)err);
  		return false;
  	}
  	if (noErr != (err = connect_next_node()))
  	{
  		osd_printf_error(
  				"Failed to connect sound format converter in AudioUnit graph (%ld)\n",
  				(long)err);
  		return false;
  	}
  	m_node_count++;
  	return true;
  }
  
  
  bool sound_coreaudio::add_effect(char const *name)
  {
  	OSStatus err;
  
  	if (!*name || !strcmp(name, OSDOPTVAL_NONE))
  		return true;
  
  	CFPropertyListRef const properties = load_property_list(name);
  	if (nullptr == properties)
  		return true;
  
  	OSType type, subtype, manufacturer;
  	CFPropertyListRef class_info;
  	if (!extract_effect_info(name, properties, type, subtype, manufacturer, class_info))
  	{
  		CFRelease(properties);
  		return true;
  	}
  
  	osd_printf_verbose("Adding effect %s to AudioUnit graph\n", name);
  	if (noErr != (err = add_node(type, subtype, manufacturer)))
  	{
  		osd_printf_error(
  				"Failed to add effect %s to AudioUnit graph (%ld)\n",
  				name,
  				(long)err);
  		CFRelease(properties);
  		return true;
  	}
  	if (noErr != (err = get_next_node_info()))
  	{
  		osd_printf_error(
  				"Failed to obtain AudioUnit for effect %s (%ld)\n",
  				name,
  				(long)err);
  		CFRelease(properties);
  		goto remove_node_and_return_error;
  	}
  	err = AudioUnitSetProperty(
  			m_node_details[m_node_count].m_unit,
  			kAudioUnitProperty_ClassInfo,
  			kAudioUnitScope_Global,
  			0,
  			&class_info,
  			sizeof(class_info));
  	CFRelease(properties);
  	if (noErr != err)
  	{
  		osd_printf_error(
  				"Failed to configure AudioUnit effect %s (%ld)\n",
  				name,
  				(long)err);
  		goto remove_node_and_return_error;
  	}
  	{
  		AudioUnitParameter const change = {
  				m_node_details[m_node_count].m_unit,
  				kAUParameterListener_AnyParameter,
  				0,
  				0 };
  		err = AUParameterListenerNotify(nullptr, nullptr, &change);
  	}
  	if (noErr != err)
  	{
  		osd_printf_error(
  				"Failed to notify AudioUnit effect %s parameter change (%ld)\n",
  				name,
  				(long)err);
  		goto remove_node_and_return_error;
  	}
  	if (noErr != (err = connect_next_node()))
  	{
  		osd_printf_error(
  				"Failed to connect effect %s in AudioUnit graph (%ld)\n",
  				name,
  				(long)err);
  		goto remove_node_and_return_error;
  	}
  	m_node_count++;
  	return true;
  
  remove_node_and_return_error:
  	osd_printf_verbose("Removing failed effect %s from AudioUnit graph\n", name);
  	err = AUGraphRemoveNode(m_graph, m_node_details[m_node_count].m_node);
  	if (noErr != err)
  	{
  		osd_printf_error(
  				"Failed to remove effect %s from AudioUnit graph (%ld)\n",
  				name,
  				(long)err);
  		return false;
  	}
  	return true;
  }
  
  
  bool sound_coreaudio::get_output_device_id(
  		char const *name,
  		AudioDeviceID &id) const
  {
  	OSStatus err;
  	UInt32 property_size;
  
  	AudioObjectPropertyAddress const devices_addr = {
  			kAudioHardwarePropertyDevices,
  			kAudioObjectPropertyScopeGlobal,
  			kAudioObjectPropertyElementMaster };
  	err = AudioObjectGetPropertyDataSize(
  			kAudioObjectSystemObject,
  			&devices_addr,
  			0,
  			nullptr,
  			&property_size);
  	if (noErr != err)
  	{
  		osd_printf_error("Error getting size of audio device list (%ld)\n", (long)err);
  		return false;
  	}
  	property_size /= sizeof(AudioDeviceID);
  	AudioDeviceID *const devices = global_alloc_array_clear<AudioDeviceID>(property_size);
  	property_size *= sizeof(AudioDeviceID);
  	err = AudioObjectGetPropertyData(
  			kAudioObjectSystemObject,
  			&devices_addr,
  			0,
  			nullptr,
  			&property_size,
  			devices);
  	UInt32 const device_count = property_size / sizeof(AudioDeviceID);
  	if (noErr != err)
  	{
  		osd_printf_error("Error getting audio device list (%ld)\n", (long)err);
  		global_free_array(devices);
  		return false;
  	}
  
  	for (UInt32 i = 0; device_count > i; i++)
  	{
  		char *const device_uid = get_device_uid(devices[i]);
  		char *const device_name = get_device_name(devices[i]);
  		if ((nullptr == device_uid) && (nullptr == device_name))
  		{
  			osd_printf_warning(
  					"Could not get UID or name for device %lu - skipping\n",
  					(unsigned long)devices[i]);
  			continue;
  		}
  
  		UInt32 const streams = get_output_stream_count(
  				devices[i],
  				device_uid,
  				device_name);
  		if (1U > streams)
  		{
  			osd_printf_verbose(
  					"No output streams found for device %s (%s) - skipping\n",
  					(nullptr != device_name) ? device_name : "<anonymous>",
  					(nullptr != device_uid) ? device_uid : "<unknown>");
  			if (nullptr != device_uid) global_free_array(device_uid);
  			if (nullptr != device_name) global_free_array(device_name);
  			continue;
  		}
  
  		for (std::size_t j = strlen(device_uid); (0 < j) && (' ' == device_uid[j - 1]); j--)
  			device_uid[j - 1] = '\0';
  		for (std::size_t j = strlen(device_name); (0 < j) && (' ' == device_name[j - 1]); j--)
  			device_name[j - 1] = '\0';
  
  		bool const matched_uid = (nullptr != device_uid) && !strcmp(name, device_uid);
  		bool const matched_name = (nullptr != device_name) && !strcmp(name, device_name);
  		if (matched_uid || matched_name)
  		{
  			osd_printf_verbose(
  					"Matched device %s (%s) with %lu output stream(s)\n",
  					(nullptr != device_name) ? device_name : "<anonymous>",
  					(nullptr != device_uid) ? device_uid : "<unknown>",
  					(unsigned long)streams);
  		}
  		global_free_array(device_uid);
  		global_free_array(device_name);
  
  		if (matched_uid || matched_name)
  		{
  			id = devices[i];
  			global_free_array(devices);
  			return true;
  		}
  	}
  
  	osd_printf_verbose("No audio output devices match %s\n", name);
  	global_free_array(devices);
  	return false;
  }
  
  
  char *sound_coreaudio::get_device_uid(AudioDeviceID id) const
  {
  	AudioObjectPropertyAddress const uid_addr = {
  			kAudioDevicePropertyDeviceUID,
  			kAudioObjectPropertyScopeGlobal,
  			kAudioObjectPropertyElementMaster };
  	CFStringRef device_uid = nullptr;
  	UInt32 property_size = sizeof(device_uid);
  	OSStatus const err = AudioObjectGetPropertyData(
  			id,
  			&uid_addr,
  			0,
  			nullptr,
  			&property_size,
  			&device_uid);
  	if ((noErr != err) || (nullptr == device_uid))
  	{
  		osd_printf_warning(
  				"Error getting UID for audio device %lu (%ld)\n",
  				(unsigned long)id,
  				(long)err);
  		return nullptr;
  	}
  	char *const result = convert_cfstring_to_utf8(device_uid);
  	CFRelease(device_uid);
  	if (nullptr == result)
  	{
  		osd_printf_warning(
  				"Error converting UID for audio device %lu to UTF-8\n",
  				(unsigned long)id);
  	}
  	return result;
  }
  
  
  char *sound_coreaudio::get_device_name(AudioDeviceID id) const
  {
  	AudioObjectPropertyAddress const name_addr = {
  			kAudioDevicePropertyDeviceNameCFString,
  			kAudioObjectPropertyScopeGlobal,
  			kAudioObjectPropertyElementMaster };
  	CFStringRef device_name = nullptr;
  	UInt32 property_size = sizeof(device_name);
  	OSStatus const err = AudioObjectGetPropertyData(
  			id,
  			&name_addr,
  			0,
  			nullptr,
  			&property_size,
  			&device_name);
  	if ((noErr != err) || (nullptr == device_name))
  	{
  		osd_printf_warning(
  				"Error getting name for audio device %lu (%ld)\n",
  				(unsigned long)id,
  				(long)err);
  		return nullptr;
  	}
  	char *const result = convert_cfstring_to_utf8(device_name);
  	CFRelease(device_name);
  	if (nullptr == result)
  	{
  		osd_printf_warning(
  				"Error converting name for audio device %lu to UTF-8\n",
  				(unsigned long)id);
  	}
  	return result;
  }
  
  
  UInt32 sound_coreaudio::get_output_stream_count(
  		AudioDeviceID id,
  		char const *uid,
  		char const *name) const
  {
  	AudioObjectPropertyAddress const streams_addr = {
  			kAudioDevicePropertyStreams,
  			kAudioDevicePropertyScopeOutput,
  			kAudioObjectPropertyElementMaster };
  	UInt32 property_size = 0;
  	OSStatus const err = AudioObjectGetPropertyDataSize(
  			id,
  			&streams_addr,
  			0,
  			nullptr,
  			&property_size);
  	if (noErr != err)
  	{
  		osd_printf_warning(
  				"Error getting output stream count for audio device %s (%s) (%ld)\n",
  				(nullptr != name) ? name : "<anonymous>",
  				(nullptr != uid) ? uid : "<unknown>",
  				(long)err);
  		return 0;
  	}
  	return property_size / sizeof(AudioStreamID);
  }
  
  
  bool sound_coreaudio::extract_effect_info(
  		char const          *name,
  		CFPropertyListRef   properties,
  		OSType              &type,
  		OSType              &subtype,
  		OSType              &manufacturer,
  		CFPropertyListRef   &class_info) const
  {
  	if (CFDictionaryGetTypeID() != CFGetTypeID(properties))
  	{
  		osd_printf_error(
  				"%s is not a valid AudioUnit effect description: expected dictionary\n",
  				name);
  		return false;
  	}
  
  	CFDictionaryRef const desc = (CFDictionaryRef)properties;
  	CFTypeRef type_val = nullptr;
  	CFTypeRef subtype_val = nullptr;
  	CFTypeRef manufacturer_val = nullptr;
  	if (CFDictionaryContainsKey(desc, CFSTR("ComponentType"))
  		&& CFDictionaryContainsKey(desc, CFSTR("ComponentSubType"))
  		&& CFDictionaryContainsKey(desc, CFSTR("ComponentManufacturer"))
  		&& CFDictionaryContainsKey(desc, CFSTR("ClassInfo")))
  	{
  		type_val = CFDictionaryGetValue(desc, CFSTR("ComponentType"));
  		subtype_val = CFDictionaryGetValue(desc, CFSTR("ComponentSubType"));
  		manufacturer_val = CFDictionaryGetValue(desc, CFSTR("ComponentManufacturer"));
  		class_info = CFDictionaryGetValue(desc, CFSTR("ClassInfo"));
  	}
  	else if (CFDictionaryContainsKey(desc, CFSTR(kAUPresetTypeKey))
  			&& CFDictionaryContainsKey(desc, CFSTR(kAUPresetSubtypeKey))
  			&& CFDictionaryContainsKey(desc, CFSTR(kAUPresetManufacturerKey)))
  	{
  		type_val = CFDictionaryGetValue(desc, CFSTR(kAUPresetTypeKey));
  		subtype_val = CFDictionaryGetValue(desc, CFSTR(kAUPresetSubtypeKey));
  		manufacturer_val = CFDictionaryGetValue(desc, CFSTR(kAUPresetManufacturerKey));
  		class_info = properties;
  	}
  	else
  	{
  		osd_printf_error(
  				"%s is not a valid AudioUnit effect description: required properties not found\n",
  				name);
  		return false;
  	}
  
  	SInt64 type_int, subtype_int, manufacturer_int;
  	if ((nullptr == type_val)
  		|| (nullptr == subtype_val)
  		|| (nullptr == manufacturer_val)
  		|| (nullptr == class_info)
  		|| (CFNumberGetTypeID() != CFGetTypeID(type_val))
  		|| (CFNumberGetTypeID() != CFGetTypeID(subtype_val))
  		|| (CFNumberGetTypeID() != CFGetTypeID(manufacturer_val))
  		|| (CFDictionaryGetTypeID() != CFGetTypeID(class_info))
  		|| !CFNumberGetValue((CFNumberRef)type_val, kCFNumberSInt64Type, &type_int)
  		|| !CFNumberGetValue((CFNumberRef)subtype_val, kCFNumberSInt64Type, &subtype_int)
  		|| !CFNumberGetValue((CFNumberRef)manufacturer_val, kCFNumberSInt64Type, &manufacturer_int))
  	{
  		osd_printf_error(
  				"%s is not a valid AudioUnit effect description: incorrect property type(s)\n",
  				name);
  		return false;
  	}
  
  	if (kAudioUnitType_Effect != type_int)
  	{
  		osd_printf_error(
  				"%s does not describe an AudioUnit effect (type %lu, expected %lu)\n",
  				name,
  				(unsigned long)type_int,
  				(unsigned long)kAudioUnitType_Effect);
  		return false;
  	}
  
  	type = (OSType)(UInt64)type_int;
  	subtype = (OSType)(UInt64)subtype_int;
  	manufacturer = (OSType)(UInt64)manufacturer_int;
  	return true;
  }
  
  
  CFPropertyListRef sound_coreaudio::load_property_list(char const *name) const
  {
  	CFURLRef const url = CFURLCreateFromFileSystemRepresentation(
  			nullptr,
  			(UInt8 const *)name,
  			strlen(name),
  			false);
  	if (nullptr == url)
  	{
  		return nullptr;
  	}
  
  	CFDataRef data = nullptr;
  	SInt32 err;
  	Boolean const status = CFURLCreateDataAndPropertiesFromResource(
  			nullptr,
  			url,
  			&data,
  			nullptr,
  			nullptr,
  			&err);
  	CFRelease(url);
  	if (!status)
  	{
  		osd_printf_error(
  				"Error reading data from %s (%ld)\n",
  				name,
  				(long)err);
  		if (nullptr != data) CFRelease(data);
  		return nullptr;
  	}
  
  	CFStringRef msg = nullptr;
  	CFPropertyListRef const result = CFPropertyListCreateFromXMLData(
  			nullptr,
  			data,
  			kCFPropertyListImmutable,
  			&msg);
  	CFRelease(data);
  	if ((nullptr == result) || (nullptr != msg))
  	{
  		char *buf = (nullptr != msg) ? convert_cfstring_to_utf8(msg) : nullptr;
  		if (nullptr != msg)
  			CFRelease(msg);
  
  		if (nullptr != buf)
  		{
  			osd_printf_error(
  					"Error creating property list from %s: %s\n",
  					name,
  					buf);
  			global_free_array(buf);
  		}
  		else
  		{
  			osd_printf_error(
  					"Error creating property list from %s\n",
  					name);
  		}
  		if (nullptr != result) CFRelease(result);
  		return nullptr;
  	}
  
  	return result;
  }
  
  
  OSStatus sound_coreaudio::render(
  		AudioUnitRenderActionFlags  *action_flags,
  		const AudioTimeStamp        *timestamp,
  		UInt32                      bus_number,
  		UInt32                      number_frames,
  		AudioBufferList             *data)
  {
  	uint32_t const number_bytes = number_frames * m_sample_bytes;
  	uint32_t const used = buffer_used();
  	if (m_in_underrun && (used < m_headroom))
  	{
  		memset(data->mBuffers[0].mData, 0, number_bytes);
  		return noErr;
  	}
  	m_in_underrun = false;
  	if (number_bytes > used)
  	{
  		m_in_underrun = true;
  		m_underflows++;
  		memset(data->mBuffers[0].mData, 0, number_bytes);
  		return noErr;
  	}
  
  	uint32_t const chunk = std::min(m_buffer_size - m_playpos, number_bytes);
  	copy_scaled((int8_t *)data->mBuffers[0].mData, m_buffer + m_playpos, chunk);
  	m_playpos += chunk;
  	if (m_playpos >= m_buffer_size)
  		m_playpos = 0;
  
  	if (chunk < number_bytes)
  	{
  		assert(0U == m_playpos);
  		assert(m_writepos >= (number_bytes - chunk));
  		copy_scaled((int8_t *)data->mBuffers[0].mData + chunk, m_buffer, number_bytes - chunk);
  		m_playpos += number_bytes - chunk;
  	}
  
  	return noErr;
  }
  
  
  OSStatus sound_coreaudio::render_callback(
  		void                        *refcon,
  		AudioUnitRenderActionFlags  *action_flags,
  		const AudioTimeStamp        *timestamp,
  		UInt32                      bus_number,
  		UInt32                      number_frames,
  		AudioBufferList             *data)
  {
  	return ((sound_coreaudio *)refcon)->render(action_flags, timestamp, bus_number, number_frames, data);
  }
  
  #else /* SDLMAME_MACOSX */
  	MODULE_NOT_SUPPORTED(sound_coreaudio, OSD_SOUND_PROVIDER, "coreaudio")
  #endif
  
  MODULE_DEFINITION(SOUND_COREAUDIO, sound_coreaudio)
  