[CM] snd on MacOS X

Chandrasekhar Ramakrishnan cramakri@zkm.de
Thu, 7 Apr 2005 17:01:57 +0200


Howdy all,

I noticed that snd does not quite interact with CoreAudio correctly 
when it comes to getting information about the number of channels a 
device supports. The code is good enough to deal with most stereo 
devices, but it doesn't properly handle many multi-channel devices 
(including my MOTU 2048).

The crux of the problem is that CoreAudio grants driver writers a lot 
of leeway in how they want to expose their device, but the code in 
audio.c expects the device to be exposed in only one of the many 
possible ways.

I've rewritten the relevant sections of the MacOS X part of audio.c 
(and am attaching my modifications here), but I'm still encountering 
some playback problems when there is a sample rate conversion between 
the sound file being played and the device.

I couldn't quite find the source of the problem, but I'm guessing I 
know what the source is. I wasn't able to find the main driver loop, 
but it looks like snd likes to drive devices in a blocking, push-mode, 
instead of being callback-based. Since CoreAudio is callback-based, 
there's presumably a sleep somewhere to simulate a blocking system, and 
the time of this sleep is probably determined by the buffer size. 
However, when there is an SRC, the sleep time is going to be somewhat 
off.

Is this correct? If so, could someone point me to where this 
blocking-model simulation happens?

Here is the rewritten block of code (oh, yeah -- my changes also 
require linking to a few additional frameworks: -framework CoreServices 
-framework AudioUnit -framework AudioToolbox):

/* ------------------------------- OSX 
----------------------------------------- */

/* this code based primarily on the CoreAudio headers and portaudio 
pa_mac_core.c,
  *   and to a much lesser extent, coreaudio.pdf and the HAL/Daisy 
examples.
  */

/* TODO: make bigger buffers work right -- is it possible to set the 
audio device buffer size? */
// -framework CoreServices -framework AudioUnit -framework AudioToolbox

#ifdef MAC_OSX
#define AUDIO_OK 1

/*
#include <CoreServices/CoreServices.h>
#include <CoreAudio/CoreAudio.h>
*/
/* ./System/Library/Frameworks/CoreAudio.framework/Headers/CoreAudio.h 
*/

static char* osx_error(OSStatus err)
{
   if (err == noErr) return("no error");
   switch (err)
     {
     case kAudioHardwareNoError:               return("no error");       
                   break;
     case kAudioHardwareUnspecifiedError:      return("unspecified audio 
hardware error"); break;
     case kAudioHardwareNotRunningError:       return("audio hardware 
not running");       break;
     case kAudioHardwareUnknownPropertyError:  return("unknown 
property");                 break;
     case kAudioHardwareBadPropertySizeError:  return("bad property");   
                   break;
     case kAudioHardwareBadDeviceError:        return("bad device");     
                   break;
     case kAudioHardwareBadStreamError:        return("bad stream");     
                   break;
     case kAudioHardwareIllegalOperationError: return("illegal 
operation");                break;
     case kAudioDeviceUnsupportedFormatError:  return("unsupported 
format");               break;
     case kAudioDevicePermissionsError:        return("device 
permissions error");         break;
     }
   return("unknown error");
}

char *device_name(AudioDeviceID deviceID, int input_case)
{
   OSStatus err = noErr;
   UInt32 size = 0, msize = 0, trans = 0, trans_size = 0;
   char *name = NULL, *mfg = NULL, *full_name = NULL;
   err =  AudioDeviceGetPropertyInfo(deviceID, 0, false, 
kAudioDevicePropertyDeviceName, &size, NULL);
   if (err == noErr) err =  AudioDeviceGetPropertyInfo(deviceID, 0, 
false, kAudioDevicePropertyDeviceManufacturer, &msize, NULL);
   if (err == noErr)
     {
       name = (char *)MALLOC(size + 2);
       err = AudioDeviceGetProperty(deviceID, 0, input_case, 
kAudioDevicePropertyDeviceName, &size, name);
       mfg = (char *)MALLOC(msize + 2);
       err = AudioDeviceGetProperty(deviceID, 0, input_case, 
kAudioDevicePropertyDeviceManufacturer, &msize, mfg);
       full_name = (char *)MALLOC(size + msize + 4);
#if HAVE_KAUDIODEVICEPROPERTYTRANSPORTTYPE
       trans_size = sizeof(UInt32);
       err = AudioDeviceGetProperty(deviceID, 0, input_case, 
kAudioDevicePropertyTransportType, &trans_size, &trans);
       if (err != noErr)
#endif
	trans = 0;
       if (trans == 0)
	mus_snprintf(full_name, size + msize + 4, "\n  %s: %s", mfg, name);
       else mus_snprintf(full_name, size + msize + 4, "\n  %s: %s 
('%c%c%c%c')",
			mfg, name,
			(char)((trans >> 24) & 0xff), (char)((trans >> 16) & 0xff), 
(char)((trans >> 8) & 0xff), (char)(trans & 0xff));
       FREE(name);
       FREE(mfg);
     }
   return(full_name);
}	

static int max_chans_via_stream_configuration(AudioDeviceID device, 
bool input_case)
{
   /* apparently MOTU 828 has to be different (this code from portaudio) 
*/
   UInt32 size = 0;
   Boolean writable;
   OSStatus err = noErr;
   err = AudioDeviceGetPropertyInfo(device, 0, input_case, 
kAudioDevicePropertyStreamConfiguration, &size, &writable);
   if (err == noErr)
     {
       AudioBufferList *list;
       list = (AudioBufferList *)malloc(size);
       err = AudioDeviceGetProperty(device, 0, input_case, 
kAudioDevicePropertyStreamConfiguration, &size, list);
       if (err == noErr)
	{
	  int chans = 0, i;
	  for (i = 0; i < list->mNumberBuffers; i++)
	    chans += list->mBuffers[i].mNumberChannels;
	  free(list);
	  return(chans);
	}
     }
   return(-1);
}

static void describe_audio_state_1(void)
{
   OSStatus err = noErr;
   UInt32 num_devices = 0, msize = 0, size = 0, buffer_size = 0, mute = 
0, alive = 0;
   Float32 vol;
   int i, j, k;
   pid_t hogger = 0;
   AudioDeviceID *devices = NULL;
   AudioDeviceID device, default_output, default_input;
   AudioStreamBasicDescription desc;
   AudioStreamBasicDescription *descs = NULL;
   int formats = 0, m;
   bool input_case = false;
   err = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, 
&msize, NULL);
   if (err != noErr) return;
   num_devices = msize / sizeof(AudioDeviceID);
   if (num_devices <= 0) return;
   devices = (AudioDeviceID *)MALLOC(msize);
   size = sizeof(AudioDeviceID);
   err = 
AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, 
&size, &default_input);
   if (err != noErr) default_input = 55555555; /* unsigned int -- I want 
some value that won't happen! */
   size = sizeof(AudioDeviceID);
   err = 
AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, 
&size, &default_output);
   if (err != noErr) default_output = 55555555;
   err = AudioHardwareGetProperty(kAudioHardwarePropertyDevices, &msize, 
(void *)devices);	
   mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "found %d audio 
device%s",
	       (int)num_devices, (num_devices != 1) ? "s" : "");
   pprint(audio_strbuf);
   for (m = 0; m < 2; m++)
     {
       for (i = 0; i < num_devices; i++)
	{
	  device = devices[i];
	  pprint(device_name(device, input_case));
	  if (input_case)
	    {
	      if (device == default_input)
		pprint(" (default input)");
	      else pprint(" (input)");
	    }
	  else
	    {
	      if (device == default_output)
		pprint(" (default output)");
	      else pprint(" (output)");
	    }
	  size = sizeof(pid_t);
	  err = AudioDeviceGetProperty(device, 0, input_case, 
kAudioDevicePropertyHogMode, &size, &hogger);
	  if ((err == noErr) && (hogger >= 0))
	    {
	      mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " currently owned 
(exclusively) by process %d", (int)hogger);
	      pprint(audio_strbuf);
	    }
	  size = sizeof(UInt32);
	  err = AudioDeviceGetProperty(device, 0, input_case, 
kAudioDevicePropertyDeviceIsAlive, &size, &alive);
	  if ((err == noErr) && (alive == 0))
	    pprint(" disconnected?");
	  size = sizeof(UInt32);
	  err = AudioDeviceGetProperty(device, 0, input_case, 
kAudioDevicePropertyBufferSize, &size, &buffer_size);
	  if (err != noErr) buffer_size = 0;
	  size = sizeof(AudioStreamBasicDescription);
	  err = AudioDeviceGetProperty(device, 0, input_case, 
kAudioDevicePropertyStreamFormat, &size, &desc);
	  if (err == noErr)
	    {
	      int config_chans;
	      unsigned int trans;
	      trans = (unsigned int)(desc.mFormatID);
	      mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "\n    srate: %d, 
chans: %d",
			   (int)(desc.mSampleRate),
			   (int)(desc.mChannelsPerFrame));
	      pprint(audio_strbuf);
	      config_chans = max_chans_via_stream_configuration(device, 
input_case);
	      if ((config_chans > 0) && (config_chans != 
(int)(desc.mChannelsPerFrame)))
		{
		  mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " (or %d?)", 
config_chans);
		  pprint(audio_strbuf);
		}
	      mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, ", bits/sample: 
%d, format: %c%c%c%c",
			   (int)(desc.mBitsPerChannel),
			   (trans >> 24) & 0xff, (trans >> 16) & 0xff, (trans >> 8) & 0xff, 
trans & 0xff);
	      pprint(audio_strbuf);
	      if (buffer_size > 0)
		{
		  mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, ", buf: %d", 
(int)buffer_size);
		  pprint(audio_strbuf);
		}
	      if ((int)(desc.mFormatFlags) != 0) /* assuming "PCM" here */
		{
		  int flags;
		  flags = ((int)(desc.mFormatFlags));
		  pprint("\n    flags: ");
		  mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "%s%s%s%s%s%s",
			       (flags & kLinearPCMFormatFlagIsFloat) ? "float " : "",
			       (flags & kLinearPCMFormatFlagIsBigEndian) ? "big-endian " : 
"",
			       (flags & kLinearPCMFormatFlagIsSignedInteger) ? "signed-int " 
: "",
			       (flags & kLinearPCMFormatFlagIsPacked) ? "packed " : "",
			       (flags & kLinearPCMFormatFlagIsAlignedHigh) ? "aligned-high " 
: "",
#if HAVE_KLINEARPCMFORMATFLAGISNONINTERLEAVED
			       (flags & kLinearPCMFormatFlagIsNonInterleaved) ? 
"non-interleaved " : ""
#else
		               ""
#endif
			       );
		  pprint(audio_strbuf);
		}

	      if ((int)(desc.mChannelsPerFrame) > 0)
		{
		  pprint("\n    vols:");
		  for (j = 0; j <= (int)(desc.mChannelsPerFrame); j++)
		    {
		      size = sizeof(Float32);
		      err = AudioDeviceGetProperty(device, j, input_case, 
kAudioDevicePropertyVolumeScalar, &size, &vol);
		      if (err == noErr)
			{
			  mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " %s%.3f",
				       (j == 0) ? "master: " : "",
				       vol);
			  pprint(audio_strbuf);
			}
		
		      if (j > 0)
			{
			  size = sizeof(UInt32);
			  err = AudioDeviceGetProperty(device, j, input_case, 
kAudioDevicePropertyMute, &size, &mute);
			  if ((err == noErr) && (mute == 1))
			    {
			      mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, " (muted)");
			      pprint(audio_strbuf);
			    }
			}
		    }
		}
	    }
	  size = 0;
	  err = AudioDeviceGetPropertyInfo(device, 0, input_case, 
kAudioDevicePropertyStreamFormats, &size, NULL);
	  formats = size / sizeof(AudioStreamBasicDescription);
	  if (formats > 1)
	    {
	      descs = (AudioStreamBasicDescription *)CALLOC(formats, 
sizeof(AudioStreamBasicDescription));
	      size = formats * sizeof(AudioStreamBasicDescription);
	      err = AudioDeviceGetProperty(device, 0, input_case, 
kAudioDevicePropertyStreamFormats, &size, descs);
	      if (err == noErr)
		{
		  mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "\n    This device 
supports %d formats: ", formats);
		  pprint(audio_strbuf);
		  for (k = 0; k < formats; k++)
		    {
		      unsigned int trans;
		      trans = (unsigned int)(descs[k].mFormatID);
		      mus_snprintf(audio_strbuf, PRINT_BUFFER_SIZE, "\n      srate: 
%d, chans: %d, bits/sample: %d, format: %c%c%c%c",
				   (int)(descs[k].mSampleRate),
				   (int)(descs[k].mChannelsPerFrame),
				   (int)(descs[k].mBitsPerChannel),
				   (trans >> 24) & 0xff, (trans >> 16) & 0xff, (trans >> 8) & 0xff, 
trans & 0xff);					
		      pprint(audio_strbuf);
		    }
		}
	      FREE(descs);
	    }
	  pprint("\n");
	}
       input_case = true;
     }
   if (devices) FREE(devices);
}

#define MAX_BUFS 4
static char **bufs = NULL;
static int in_buf = 0, out_buf = 0;

/* No longer needed
static OSStatus writer(AudioDeviceID inDevice,
		       const AudioTimeStamp *inNow,
		       const AudioBufferList *InputData, const AudioTimeStamp 
*InputTime,
		       AudioBufferList *OutputData, const AudioTimeStamp *OutputTime,
		       void *appGlobals)
{
   AudioBuffer abuf;
   char *aplbuf, *sndbuf;
   abuf = OutputData->mBuffers[0];
   aplbuf = (char *)(abuf.mData);
   sndbuf = bufs[out_buf];
   memmove((void *)aplbuf, (void *)sndbuf, abuf.mDataByteSize);
   out_buf++;
   if (out_buf >= MAX_BUFS) out_buf = 0;
   return(noErr);
}
*/

static OSStatus reader(AudioDeviceID inDevice,
		       const AudioTimeStamp *inNow,
		       const AudioBufferList *InputData, const AudioTimeStamp 
*InputTime,
		       AudioBufferList *OutputData, const AudioTimeStamp *OutputTime,
		       void *appGlobals)
{
   AudioBuffer abuf;
   char *aplbuf, *sndbuf;
   abuf = InputData->mBuffers[0];
   aplbuf = (char *)(abuf.mData);
   sndbuf = bufs[out_buf];
   memmove((void *)sndbuf, (void *)aplbuf, abuf.mDataByteSize);
   out_buf++;
   if (out_buf >= MAX_BUFS) out_buf = 0;
   return(noErr);
}

// BEGIN CR CHANGED

// Use a default output unit for the output
static AudioUnit gDefaultOutputUnit = NULL;

// Audio Converter used, not to actually convert
// data, but to avoid having to implement a ring buffer (see comment
// in CreateAudioConverter)
AudioConverterRef gOutputConverter;

static unsigned int bufsize = 0, current_bufsize = 0;
static UInt32 bufsize_in_frames = 0;

OSStatus	AudioConverterWriter( 	AudioConverterRef	converter,
									UInt32*				outDataSize,
									void**				outData,
									void*				inRefCon)

{
	char *sndbuf;
	sndbuf = bufs[out_buf];
	out_buf++;
	if (out_buf >= MAX_BUFS) out_buf = 0;		

	*outData = sndbuf;
	*outDataSize = bufsize;
	
	return noErr;
}

OSStatus	AudioUnitWriter( 	void 						*inRefCon,
								AudioUnitRenderActionFlags 	*ioActionFlags,
								const AudioTimeStamp 		*inTimeStamp,
								UInt32 						inBusNumber,
								UInt32 						inNumberFrames,
								AudioBufferList 			*ioData)

{
/**
  *  This would work if it were not for the potential sample rate 
conversion
  *
  */
	AudioBuffer abuf;
	char *aplbuf, *sndbuf;
	abuf = ioData->mBuffers[0];
	aplbuf = (char *)(abuf.mData);
	sndbuf = bufs[out_buf];
	memmove((void *)aplbuf, (void *)sndbuf, abuf.mDataByteSize);
	out_buf++;
	if (out_buf >= MAX_BUFS) out_buf = 0;	
	return noErr;

/**
  *  This should work once we figure out how often SND writes
  *  data to its buffers and adjust this rate to any potential
  *  sample rate conversion
  *
	UInt32 size = ioData->mBuffers[0].mDataByteSize;
	OSStatus err =
		AudioConverterFillBuffer(	gOutputConverter,
									AudioConverterWriter,
									0,
									&size,
									ioData->mBuffers[0].mData);
	return err;
*/	
}

int		CreateDefaultAU()
{
	OSStatus err = noErr;

	// Open the default output unit
	ComponentDescription desc;
	desc.componentType = kAudioUnitType_Output;
	desc.componentSubType = kAudioUnitSubType_DefaultOutput;
	desc.componentManufacturer = kAudioUnitManufacturer_Apple;
	desc.componentFlags = 0;
	desc.componentFlagsMask = 0;
	
	Component comp = FindNextComponent(NULL, &desc);
	if (comp == NULL)
	{
		fprintf(stderr,
			"open audio output (find output AU) err: %d (%4.4s) %s\n",
			(int)err, (char*)&err, osx_error(err));	
		return MUS_ERROR;
	}
	
	
	err = OpenAComponent(comp, &gDefaultOutputUnit);
	if (gDefaultOutputUnit == NULL)
	{
		fprintf(stderr,
			"open audio output (create output AU) err: %d (%4.4s) %s\n",
			(int)err, (char*)&err, osx_error(err));	
		return MUS_ERROR;
	}

	// Set up a callback function to generate output to the output unit
     AURenderCallbackStruct input;
	input.inputProc = AudioUnitWriter;
	input.inputProcRefCon = NULL;

	err = AudioUnitSetProperty (gDefaultOutputUnit,
								kAudioUnitProperty_SetRenderCallback,
								kAudioUnitScope_Input,
								0,
								&input,
								sizeof(input));
	if (err)
	{
		fprintf(stderr,
			"open audio output (set AU callback) err: %d (%4.4s) %s\n",
			(int)err, (char*)&err, osx_error(err));	
		return MUS_ERROR;
	}	

     return MUS_NO_ERROR;

}

int 	CreateAudioConverter(AudioStreamBasicDescription* device_desc)
{
	// This admittedly looks pretty weird, creating a format converter
	// that converts to the same format, but I do this because I need
	// the bufs to be a ring buffer. Instead of implement my own ring 
buffer,
	// I'll just take advantage of the audio converter to get this behavior
	
	OSStatus err;
	
	if (gOutputConverter) {
		err = AudioConverterDispose(gOutputConverter);
		if (err) {
			fprintf(stderr,"open audio output (dispose audio converter) failed 
%i (0x%x / %4.4s) %s\n",
			(int)err, (int)err, (char*)&err, osx_error(err));
			return(MUS_ERROR);		
		}
	}
		
	err = AudioConverterNew(device_desc, device_desc, &gOutputConverter);
	if (err) {
		fprintf(stderr,"open audio output (create audio converter) failed %i 
(0x%x / %4.4s) %s\n",
		(int)err, (int)err, (char*)&err, osx_error(err));
		return(MUS_ERROR);		
	}
}

// END CR CHANGED


static AudioDeviceID device = kAudioDeviceUnknown;
static bool writing = false, open_for_input = false;


int mus_audio_close(int line)
{
   OSStatus err = noErr;
   UInt32 sizeof_running;
   UInt32 running;
   if (open_for_input)
     {
       in_buf = 0;
       err = AudioDeviceStop(device, (AudioDeviceIOProc)reader);
       if (err == noErr)
	err = AudioDeviceRemoveIOProc(device, (AudioDeviceIOProc)reader);
     }
   else // open for output
   {
// BEGIN CR CHANGED	
       if ((in_buf > 0) && (!writing))
	  {
		  /* short enough sound that we never got started? */
		  err = AudioUnitInitialize(gDefaultOutputUnit);
		  if (!err)
		  	err = AudioOutputUnitStart(gDefaultOutputUnit);		
		  if (err == noErr)
			writing = true;
	  }
	
       if (writing)
	  {

		  /* send out waiting buffers */
/*		
		  sizeof_running = sizeof(UInt32);
		  while (in_buf == out_buf)
			{
			  err = AudioDeviceGetProperty(device, 0, false, 
kAudioDevicePropertyDeviceIsRunning, &sizeof_running, &running);
			}
		  while (in_buf != out_buf)
			{
			  err = AudioDeviceGetProperty(device, 0, false, 
kAudioDevicePropertyDeviceIsRunning, &sizeof_running, &running);
			}
		  in_buf = 0;
		  err = AudioDeviceStop(device, (AudioDeviceIOProc)writer);
		  if (err == noErr)
			err = AudioDeviceRemoveIOProc(device, (AudioDeviceIOProc)writer);
		  writing = false;
*/
		  err = AudioOutputUnitStop(gDefaultOutputUnit);
		  if (!err)
		  	err = AudioUnitUninitialize(gDefaultOutputUnit);
		  writing = false;
// END CR CHANGED	
	  }
   }
   device = kAudioDeviceUnknown;
   if (err == noErr)
     return(MUS_NO_ERROR);
   return(MUS_ERROR);
}

typedef enum {CONVERT_NOT, CONVERT_COPY, CONVERT_SKIP, 
CONVERT_COPY_AND_SKIP, CONVERT_SKIP_N, CONVERT_COPY_AND_SKIP_N} 
audio_convert_t;
static audio_convert_t conversion_choice = CONVERT_NOT;
static float conversion_multiplier = 1.0;
static int dac_out_chans, dac_out_srate;
static int incoming_out_chans = 1, incoming_out_srate = 44100;

static int fill_point = 0;
// CR CHANGED -- Moved declaration of bufsize up a bit
//   because I need it in a function declared above


int mus_audio_open_output(int dev, int srate, int chans, int format, 
int size)
{

// BEGIN CR CHANGED	
	// We need to work with both the device and the AudioUnit, since
	// some properties can only be set directly on the device (like 
BufferSize)
	// and others, on the AudioUnit.
	OSStatus err = noErr;
	UInt32 paramSize;
	UInt32 sizeof_device, sizeof_format, sizeof_bufframes;
	AudioStreamBasicDescription device_desc;
	sizeof_device = sizeof(AudioDeviceID);
	sizeof_bufframes = sizeof(UInt32);
	
	// create the DefaultOutput AU (if necessary)
	if (NULL == gDefaultOutputUnit) {
		int result = CreateDefaultAU();
		if (MUS_NO_ERROR != result)
			return result;
	}
	
	// get the device and, from the device, the buffer size (in frames)
	err = 
AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, 
&sizeof_device, (void *)(&device));
	
	if (!err)
		err = AudioDeviceGetProperty(device, 0, false, 
kAudioDevicePropertyBufferFrameSize, &sizeof_bufframes, 
&bufsize_in_frames);
	if (err)
	{
		fprintf(stderr,"open audio output err: %d %s\n", (int)err, 
osx_error(err));
		return(MUS_ERROR);
	}
	
	
	// set-up the format we want to use
	device_desc.mSampleRate = srate;						//	the sample rate of the audio 
stream
	device_desc.mFormatID = kAudioFormatLinearPCM;			//	the specific 
encoding type of audio stream
	device_desc.mFormatFlags = 								//	flags specific to each format
		kLinearPCMFormatFlagIsFloat 						//  interleaved, big-endian floats
		| kLinearPCMFormatFlagIsBigEndian
		| kLinearPCMFormatFlagIsPacked;			
	device_desc.mBytesPerPacket = sizeof(float) * chans;	//  packet is the 
same as frame in this case (LPCM)
	device_desc.mFramesPerPacket = 1;						//	see above
	device_desc.mBytesPerFrame = sizeof(float) * chans;		//	bytes per 
float times num channels
	device_desc.mChannelsPerFrame = chans;					//  num channels
	device_desc.mBitsPerChannel = sizeof(float) * 8; 		//  bytes per float 
times num bits per byte
		
	// set the format	
	err = AudioUnitSetProperty(	gDefaultOutputUnit,
								kAudioUnitProperty_StreamFormat,
								kAudioUnitScope_Input,
								0,
								&device_desc,
								sizeof(AudioStreamBasicDescription));
	
	// get the nominal sample rate of the devce, so we can factor in any
	// sample rate conversions into the buffer size
	Float64 deviceNominalSampleRate = 0.0;
	paramSize = sizeof(deviceNominalSampleRate);
	err = AudioDeviceGetProperty(device, 0, false, 
kAudioDevicePropertyNominalSampleRate, &paramSize, 
&deviceNominalSampleRate);
	if (err)
	{
		fprintf(stderr,"open audio output err (get sample rate): %d 0x%x 
%4.4s %s\n",
			err, err, (char*)&err, osx_error(err));
		return(MUS_ERROR);
	}
	
	// set the buffer size (in bytes), with some correction for sample 
rate conversion
	bufsize = bufsize_in_frames * device_desc.mBytesPerFrame;
								
	if (err)
	{
		fprintf(stderr,
			"open audio output err setting stream format: %i (0x%x / %4.4s) 
%s\n",
			(int)err, (int)err, (char*)&err, osx_error(err));
		return(MUS_ERROR);
	}	
	
	
	/* now check for srate/chan mismatches and so on */	
	if (err != noErr)
	{
	  fprintf(stderr,"open audio output (get device format) err: %d %s\n", 
(int)err, osx_error(err));
	  return(MUS_ERROR);
	}
	/* current DAC state: device_desc.mChannelsPerFrame, 
(int)(device_desc.mSampleRate) */
	if ((device_desc.mChannelsPerFrame != chans) ||
	  ((int)(device_desc.mSampleRate) != srate))
	{
		// this shouldn't happen
		fprintf(stderr,"open audio output (set device format) failed\n");
		return(MUS_ERROR);
	}
	
	if (MUS_ERROR == CreateAudioConverter(&device_desc))
		return(MUS_ERROR);
	
	/* now DAC claims it is ready for device_desc.mChannelsPerFrame, 
(int)(device_desc.mSampleRate) */
	dac_out_chans = device_desc.mChannelsPerFrame; /* use better variable 
names */
	dac_out_srate = (int)(device_desc.mSampleRate);
	open_for_input = false;

	
// END CR CHANGED	
	if ((bufs == NULL) || (bufsize > current_bufsize))
	{
	  int i;
	  if (bufs)
	{
	  for (i = 0; i < MAX_BUFS; i++) FREE(bufs[i]);
	  FREE(bufs);
	}
	  bufs = (char **)CALLOC(MAX_BUFS, sizeof(char *));
	  for (i = 0; i < MAX_BUFS; i++)
	bufs[i] = (char *)CALLOC(bufsize, sizeof(char));
	  current_bufsize = bufsize;
	}
	in_buf = 0;
	out_buf = 0;
	fill_point = 0;
	incoming_out_srate = srate;
	incoming_out_chans = chans;
	if (incoming_out_chans == dac_out_chans)
	{
	  if (incoming_out_srate == dac_out_srate)
	{
	  conversion_choice = CONVERT_NOT;
	  conversion_multiplier = 1.0;
	}
	  else
	{
	  /* here we don't get very fancy -- assume dac/2=in */
	  conversion_choice = CONVERT_COPY;
	  conversion_multiplier = 2.0;
	}
	}
	else
	{
	  if (incoming_out_srate == dac_out_srate)
	{
	  if ((dac_out_chans == 2) && (incoming_out_chans == 1)) /* the usual 
case */
		{
		  conversion_choice = CONVERT_SKIP;
		  conversion_multiplier = 2.0;
		}
	  else
		{
		  conversion_choice = CONVERT_SKIP_N;
		  conversion_multiplier = ((float)dac_out_chans / 
(float)incoming_out_chans);
		}
	}
	  else
	{
	  if ((dac_out_chans == 2) && (incoming_out_chans == 1)) /* the usual 
case */
		{
		  conversion_choice = CONVERT_COPY_AND_SKIP;
		  conversion_multiplier = 4.0;
		}
	  else
		{
		  conversion_choice = CONVERT_COPY_AND_SKIP_N;
		  conversion_multiplier = ((float)dac_out_chans / 
(float)incoming_out_chans) * 2;
		}
	}
	}
	return(MUS_NO_ERROR);
}

static void convert_incoming(char *to_buf, int fill_point, int lim, 
char *buf)
{
   int i, j, k, jc, kc, ic;
   switch (conversion_choice)
     {
     case CONVERT_NOT:
       /* no conversion needed */
       for (i = 0; i < lim; i++)
	to_buf[i + fill_point] = buf[i];
       break;
     case CONVERT_COPY:
       /* copy sample to mimic lower srate */
       for (i = 0, j = fill_point; i < lim; i += 8, j += 16)
	for (k = 0; k < 8; k++)
	  {
	    to_buf[j + k] = buf[i + k];
	    to_buf[j + k + 8] = buf[i + k];
	  }
       break;
     case CONVERT_SKIP:
       /* skip sample for empty chan */
       for (i = 0, j = fill_point; i < lim; i += 4, j += 8)
	for (k = 0; k < 4; k++)
	  {
	    to_buf[j + k] = buf[i + k];
	    to_buf[j + k + 4] = 0;
	  }
       break;
     case CONVERT_SKIP_N:
       /* copy incoming_out_chans then skip up to dac_out_chans */
       jc = dac_out_chans * 4;
       ic = incoming_out_chans * 4;
       for (i = 0, j = fill_point; i < lim; i += ic, j += jc)
	{
	  for (k = 0; k < ic; k++) to_buf[j + k] = buf[i + k];
	  for (k = ic; k < jc; k++) to_buf[j + k] = 0;
	}
       break;
     case CONVERT_COPY_AND_SKIP:
       for (i = 0, j = fill_point; i < lim; i += 4, j += 16)
	for (k = 0; k < 4; k++)
	  {
	    to_buf[j + k] = buf[i + k];
	    to_buf[j + k + 4] = 0;
	    to_buf[j + k + 8] = buf[i + k];
	    to_buf[j + k + 12] = 0;
	  }
       break;
     case CONVERT_COPY_AND_SKIP_N:
       /* copy for each active chan, skip rest */
       jc = dac_out_chans * 8;
       ic = incoming_out_chans * 4;
       kc = dac_out_chans * 4;
       for (i = 0, j = fill_point; i < lim; i += ic, j += jc)
	{
	  for (k = 0; k < ic; k++)
	    {
	      to_buf[j + k] = buf[i + k];
	      to_buf[j + k + kc] = buf[i + k];	
	    }
	  for (k = ic; k < kc; k++)
	    {
	      to_buf[j + k] = 0;
	      to_buf[j + k + kc] = 0;
	    }
	}
       break;
     }
}

int mus_audio_write(int line, char *buf, int bytes)
{
   OSStatus err = noErr;
   int lim, bp, out_bytes;
   UInt32 sizeof_running;
   UInt32 running;
   char *to_buf;
   to_buf = bufs[in_buf];
   out_bytes = (int)(bytes * conversion_multiplier);
   if ((fill_point + out_bytes) > bufsize)
     out_bytes = bufsize - fill_point;
   lim = (int)(out_bytes / conversion_multiplier);
   if (!writing)
     {
       convert_incoming(to_buf, fill_point, lim, buf);
       fill_point += out_bytes;
       if (fill_point >= bufsize)
	{
	  in_buf++;
	  fill_point = 0;
	  if (in_buf == MAX_BUFS)
	    {
// BEGIN CR CHANGED	
	      in_buf = 0;
		  err = AudioUnitInitialize(gDefaultOutputUnit);
		  if (!err)
		  	err = AudioOutputUnitStart(gDefaultOutputUnit);		
		  if (err == noErr)
			{
			  writing = true;
			  return(MUS_NO_ERROR);
			}
	      else return(MUS_ERROR);
	    }
// END CR CHANGED
	}
       return(MUS_NO_ERROR);
     }
   if ((fill_point == 0) && (in_buf == out_buf))
     {
       bp = out_buf;
       sizeof_running = sizeof(UInt32);
       while (bp == out_buf)
	{
	  /* i.e. just kill time without hanging */
	  err = AudioDeviceGetProperty(device, 0, false, 
kAudioDevicePropertyDeviceIsRunning, &sizeof_running, &running);
	  /* usleep(10); */
	}
     }
   to_buf = bufs[in_buf];
   if (fill_point == 0) memset((void *)to_buf, 0, bufsize);
   convert_incoming(to_buf, fill_point, lim, buf);
   fill_point += out_bytes;
   if (fill_point >= bufsize)
     {
       in_buf++;
       fill_point = 0;
       if (in_buf >= MAX_BUFS) in_buf = 0;
     }
   return(MUS_NO_ERROR);
}

int mus_audio_open_input(int dev, int srate, int chans, int format, int 
size)
{
   OSStatus err = noErr;
   UInt32 sizeof_device;
   UInt32 sizeof_bufsize;
   sizeof_device = sizeof(AudioDeviceID);
   sizeof_bufsize = sizeof(unsigned int);
   err = 
AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, 
&sizeof_device, (void *)(&device));
   bufsize = 4096;
   if (err == noErr)
     err = AudioDeviceGetProperty(device, 0, true, 
kAudioDevicePropertyBufferSize, &sizeof_bufsize, &bufsize);
   if (err != noErr)
     {
       fprintf(stderr,"open audio input err: %d %s\n", (int)err, 
osx_error(err));
       return(MUS_ERROR);
     }
   open_for_input = true;
   /* assume for now that recorder (higher level) will enforce match */
   if ((bufs == NULL) || (bufsize > current_bufsize))
     {
       int i;
       if (bufs)
	{
	  for (i = 0; i < MAX_BUFS; i++) FREE(bufs[i]);
	  FREE(bufs);
	}
       bufs = (char **)CALLOC(MAX_BUFS, sizeof(char *));
       for (i = 0; i < MAX_BUFS; i++)
	bufs[i] = (char *)CALLOC(bufsize, sizeof(char));
       current_bufsize = bufsize;
     }
   in_buf = 0;
   out_buf = 0;
   fill_point = 0;
   incoming_out_srate = srate;
   incoming_out_chans = chans;
   err = AudioDeviceAddIOProc(device, (AudioDeviceIOProc)reader, NULL);
   if (err == noErr)
     err = AudioDeviceStart(device, (AudioDeviceIOProc)reader);
   if (err != noErr)
     {
       fprintf(stderr,"add open audio input err: %d %s\n", (int)err, 
osx_error(err));
       return(MUS_ERROR);
     }
   return(MUS_NO_ERROR);
}

int mus_audio_read(int line, char *buf, int bytes)
{
   OSStatus err = noErr;
   int bp;
   UInt32 sizeof_running;
   UInt32 running;
   char *to_buf;
   if (in_buf == out_buf)
     {
       bp = out_buf;
       sizeof_running = sizeof(UInt32);
       while (bp == out_buf)
	{
	  err = AudioDeviceGetProperty(device, 0, true, 
kAudioDevicePropertyDeviceIsRunning, &sizeof_running, &running);
	  if (err != noErr)
	    fprintf(stderr,"wait err: %s ", osx_error(err));
	}
     }
   to_buf = bufs[in_buf];
   if (bytes <= bufsize)
     memmove((void *)buf, (void *)to_buf, bytes);
   else memmove((void *)buf, (void *)to_buf, bufsize);
   in_buf++;
   if (in_buf >= MAX_BUFS) in_buf = 0;
   return(MUS_ERROR);
}

static int max_chans(AudioDeviceID device, int input)
{
   int maxc = 0, formats, k, config_chans;
   UInt32 size;
   OSStatus err;
   AudioStreamBasicDescription desc;
   AudioStreamBasicDescription *descs;
   size = sizeof(AudioStreamBasicDescription);
   err = AudioDeviceGetProperty(device, 0, input, 
kAudioDevicePropertyStreamFormat, &size, &desc);
   if (err == noErr)
     {
       maxc = (int)(desc.mChannelsPerFrame);
       size = 0;
       err = AudioDeviceGetPropertyInfo(device, 0, input, 
kAudioDevicePropertyStreamFormats, &size, NULL);
       formats = size / sizeof(AudioStreamBasicDescription);
       if (formats > 1)
	{
	  descs = (AudioStreamBasicDescription *)CALLOC(formats, 
sizeof(AudioStreamBasicDescription));
	  size = formats * sizeof(AudioStreamBasicDescription);
	  err = AudioDeviceGetProperty(device, 0, input, 
kAudioDevicePropertyStreamFormats, &size, descs);
	  if (err == noErr)
	    for (k = 0; k < formats; k++)
	      if ((int)(descs[k].mChannelsPerFrame) > maxc) maxc = 
(int)(descs[k].mChannelsPerFrame);
	  FREE(descs);
	}
     }
   else fprintf(stderr, "read chans hit: %s\n", osx_error(err));
   config_chans = max_chans_via_stream_configuration(device, input);
   if (config_chans > maxc) return(config_chans);
   return(maxc);
}

int mus_audio_mixer_read(int dev1, int field, int chan, float *val)
{
   AudioDeviceID dev = kAudioDeviceUnknown;
   OSStatus err = noErr;
   UInt32 size;
   Float32 amp;
   int i, curdev;
   bool in_case = false;
   switch (field)
     {
     case MUS_AUDIO_AMP:
       size = sizeof(AudioDeviceID);
       err = 
AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, 
&size, &dev);
       size = sizeof(Float32);
       err = AudioDeviceGetProperty(dev, chan + 1, false, 
kAudioDevicePropertyVolumeScalar, &size, &amp);
       if (err == noErr)
	val[0] = (Float)amp;
       else val[0] = 0.0;
       break;
     case MUS_AUDIO_CHANNEL:
       curdev = MUS_AUDIO_DEVICE(dev1);
       size = sizeof(AudioDeviceID);
       in_case = ((curdev == MUS_AUDIO_MICROPHONE) || (curdev == 
MUS_AUDIO_LINE_IN));
       if (in_case)
	err = 
AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, 
&size, &dev);
       else err = 
AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, 
&size, &dev);
       if (err != noErr) fprintf(stderr, "get default: %s\n", 
osx_error(err));
       val[0] = max_chans(dev, in_case);
       break;
     case MUS_AUDIO_SRATE:
       val[0] = 44100;
       break;
     case MUS_AUDIO_FORMAT:
       /* never actually used except perhaps play.scm */
       val[0] = 1.0;
       val[1] = MUS_BFLOAT;
       break;
     case MUS_AUDIO_PORT:
       i = 0;
       if (1 < chan) val[1] = MUS_AUDIO_MICROPHONE;
       if (2 < chan) val[2] = MUS_AUDIO_DAC_OUT;
       val[0] = 2;
       break;
     case MUS_AUDIO_SAMPLES_PER_CHANNEL:
       /* bufsize / 16: mulaw 22050 mono -> float 44100 stereo => 16:1 
expansion */
       {
	int bufsize = 4096;
	UInt32 sizeof_bufsize;
	sizeof_bufsize = sizeof(unsigned int);
	curdev = MUS_AUDIO_DEVICE(dev1);
	size = sizeof(AudioDeviceID);
	in_case = ((curdev == MUS_AUDIO_MICROPHONE) || (curdev == 
MUS_AUDIO_LINE_IN));
	if (in_case)
	  err = 
AudioHardwareGetProperty(kAudioHardwarePropertyDefaultInputDevice, 
&size, &dev);
	else err = 
AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, 
&size, &dev);
	if (err != noErr)
	  fprintf(stderr, "get samps/chan: %s\n", osx_error(err));
	else
	  {
	    err = AudioDeviceGetProperty(dev, 0, true, 
kAudioDevicePropertyBufferSize, &sizeof_bufsize, &bufsize);
	    if (err == noErr) val[0] = (float)(bufsize / 16);
	  }
       }
       break;
     default:
       return(MUS_ERROR);
       break;
     }
   return(MUS_NO_ERROR);
}

int mus_audio_mixer_write(int dev1, int field, int chan, float *val)
{
   AudioDeviceID dev = kAudioDeviceUnknown;
   OSStatus err = noErr;
   Boolean writable;
   UInt32 size;
   Float32 amp;
   switch (field)
     {
     case MUS_AUDIO_AMP:
       size = sizeof(AudioDeviceID);
       err = 
AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, 
&size, (void *)(&dev));
       err = AudioDeviceGetPropertyInfo(dev, chan + 1, false, 
kAudioDevicePropertyVolumeScalar, NULL, &writable); /* "false" -> 
output */
       amp = (Float32)(val[0]);
       if ((err == kAudioHardwareNoError) && (writable))
	err = AudioDeviceSetProperty(dev, NULL, chan + 1, false, 
kAudioDevicePropertyVolumeScalar, sizeof(Float32), &amp);
       break;
     default:
       return(MUS_ERROR);
       break;
     }
   return(MUS_NO_ERROR);
}

int mus_audio_initialize(void) {return(MUS_NO_ERROR);}
int mus_audio_systems(void) {return(1);}
char *mus_audio_system_name(int system) {return("Mac OSX");}

char *mus_audio_moniker(void) {return("Mac OSX audio");}
#endif


--
C. Ramakrishnan        cramakri@zkm.de
ZKM | Zentrum fuer Kunst und Medientechnologie
Institut fuer Musik und Akustik