//
// http://developer.apple.com/library/mac/#documentation/MusicAudio/Reference/CACoreAudioReference
//
#include <Carbon/Carbon.h>

#include "cmPrefix.h"
#include "cmGlobal.h"
#include "cmRpt.h"
#include "cmAudioPort.h"
#include "cmMem.h"
#include "cmMallocDebug.h"
#include "cmAudioPortOsx.h"

#include <CoreAudio/CoreAudio.h>
#include <unistd.h> // usleap

typedef struct 
{
  unsigned chIdx;
  unsigned chCnt;
  unsigned framesPerBuf;
} cmApOsxBufRecd;

typedef struct
{
  AudioDeviceID       devId;
  AudioDeviceIOProcID ioProcId;
  unsigned            devIdx;
  cmApCallbackPtr_t   callbackPtr;

  char*               mfgStr;
  char*               nameStr;
  double              srate;

  unsigned            inFramesPerCycle;
  unsigned            inChCnt;
  unsigned            inPktCnt;
  cmApAudioPacket_t*  inPktArray;

  unsigned            outFramesPerCycle;
  unsigned            outChCnt;
  unsigned            outPktCnt;
  cmApAudioPacket_t*  outPktArray;

  unsigned            timeOutMs;
  AudioDeviceID       fmtChangeDevId;
  AudioStreamID       fmtChangeStreamId;

} cmApOsxDevRecd;

typedef struct
{
  cmRpt_t*      rpt;
  cmApOsxDevRecd*  devArray;
  unsigned      devCnt;
} cmApOsxRoot;

cmApOsxRoot _cmApOsxRoot = { NULL, NULL, 0 };



const char* _cmApOsxOsStatusToText( OSStatus errCode )
{
	switch( errCode )
	{
		case kAudioHardwareNoError:                   return "No Error"; 						
		case kAudioHardwareNotRunningError:           return "Not runing error";	
		case kAudioHardwareUnspecifiedError:          return "Unspecified error";
		case kAudioHardwareUnknownPropertyError:      return "Unknown property error";
		case kAudioHardwareBadPropertySizeError:      return "Bad property error";
		case kAudioHardwareIllegalOperationError:     return "Illegal operation error";	
		case kAudioHardwareBadDeviceError:            return "Bad device error";	
		case kAudioHardwareBadStreamError:            return "Bad stream error";
		case kAudioHardwareUnsupportedOperationError:	return "Unsupported operating error";
		case kAudioDeviceUnsupportedFormatError:      return "Unsupported format error";
		case kAudioDevicePermissionsError:            return "Permissions error";
	}
	return "Unknown error code";
}


cmApRC_t _cmApOsxOsErrorRpt( OSStatus err, cmApOsxRoot* r, const char* func, const char* file, int line )
{
  if( r->rpt != NULL )
  {
    if( err != noErr )
      cmRptErrorf(r->rpt,"Audio Port Error %s in %s line:%i %s\n",_cmApOsxOsStatusToText(err),func,line,file);
    else
      cmRptErrorf(r->rpt,"Audio Port Error: Unknown\n");
  }
	
	return kSysErrApRC;
}

#define _cmApOsxOsError( err, root ) _cmApOsxOsErrorRpt( err, root, __FUNCTION__, __FILE__, __LINE__ )

OSStatus _cmApOsxAllocDeviceCFString( AudioDeviceID devId, UInt32 ch, Boolean inputFl, AudioDevicePropertyID devPropId, char** strPtrPtr  ) 
{
  CFStringRef  cfStr;
	//Boolean      outWritableFl = false;
	UInt32       outByteCnt    = sizeof(cfStr);;
	OSStatus     err           = noErr;
  
	if((err = AudioDeviceGetProperty( devId, ch, inputFl, devPropId, &outByteCnt, &cfStr )) != noErr )
		return err;

  CFIndex cfLen = CFStringGetLength(cfStr) * 2;

  char* cStr = cmMemAllocZ( char, cfLen+1 );
  cStr[0] = 0;
  
  if( CFStringGetCString( cfStr, cStr, cfLen, kCFStringEncodingUTF8 ) )
    cStr[cfLen]=0;

  CFRelease(cfStr);
	
  *strPtrPtr = cStr;

	return noErr;
}

// Note: the streamIdArray* allocated by this function must be released by the calling function.
OSStatus _cmApOsxAllocStreamIdArray( AudioDeviceID devId, Boolean inputFl, AudioStreamID** streamIdArrayPtr, unsigned* streamIdCntPtr )
{
  UInt32   byteCnt    = 0;
  Boolean  canWriteFl = false;
	OSStatus err        = noErr;

  *streamIdArrayPtr = NULL;
  *streamIdCntPtr   = 0;

  // get the length of the stream id array
	if((err = AudioDeviceGetPropertyInfo( devId, 0, inputFl, kAudioDevicePropertyStreams, &byteCnt, &canWriteFl )) != noErr )
    return err;
	
	if( byteCnt <= 0 )
    goto doneLabel;

  // get the count of streams
  *streamIdCntPtr     = byteCnt / sizeof(AudioStreamID);

  // allocate the stream id array
  *streamIdArrayPtr = cmMemAllocZ( AudioStreamID, *streamIdCntPtr );

  // verify that the size of the stream id array is an integer multiple of the sizeof(AudioStreamID)
  assert( *streamIdCntPtr * sizeof(AudioStreamID) == byteCnt );
	
  // fill the stream id array
  if((err = AudioDeviceGetProperty( devId, 0, inputFl, kAudioDevicePropertyStreams, &byteCnt, *streamIdArrayPtr )) != noErr )
    return err;

 doneLabel:
  return noErr;
}

OSStatus 	cmApOsxSystemStreamPropertyListenerProc(
											    AudioStreamID         inStream, 
											    UInt32                inChannel, 
											    AudioDevicePropertyID inPropertyID, 
											    void*                 inClientData)
{
  cmApOsxDevRecd* drp = (cmApOsxDevRecd*)inClientData;

  drp->fmtChangeDevId    = drp->devId;
  drp->fmtChangeStreamId = inStream;

  return noErr;
}											    


OSStatus _cmApOsxSetSampleRate( cmApOsxDevRecd* drp, bool inputFl, double newSampleRate )
{
	AudioStreamID* streamIdArray = NULL;
  unsigned       streamCnt;
	OSStatus 			 err 		       = noErr;
	unsigned			 waitMs		     = 0;
	unsigned       i;

  // allocate a stream id array
  if((err = _cmApOsxAllocStreamIdArray( drp->devId, inputFl, &streamIdArray, &streamCnt )) != noErr )
    return _cmApOsxOsError(err,&_cmApOsxRoot);
		
	// 	for each stream on this device
	for(i=0; i<streamCnt; i++)
	{
    UInt32                   byteCnt    = 0;
		AudioStreamBasicDescription sdr;
		Boolean                     canWriteFl = false;

    // get the size of the stream desc recd
    if((err = AudioDeviceGetPropertyInfo( drp->devId, i, inputFl, kAudioDevicePropertyStreamFormat, &byteCnt, &canWriteFl )) != noErr )
    {
       _cmApOsxOsError(err,&_cmApOsxRoot);
       goto errLabel;
		}

    assert( byteCnt == sizeof(sdr) );
	
    // get the stream desc recd
    if((err = AudioDeviceGetProperty( drp->devId, i, inputFl, kAudioDevicePropertyStreamFormat, &byteCnt, &sdr )) != noErr )
    {
       _cmApOsxOsError(err,&_cmApOsxRoot);
       goto errLabel;
		}

		// if the format has not already been set
		if( sdr.mSampleRate != newSampleRate )
		{	
			// change the sample rate
      sdr.mSampleRate = newSampleRate;

      drp->fmtChangeDevId 	  = -1;
      drp->fmtChangeStreamId	= -1;

			// attempt to change the sample rate
			if((err = AudioStreamSetProperty(streamIdArray[i], NULL, 0, kAudioDevicePropertyStreamFormat, sizeof(sdr), &sdr)) != noErr )
			{
				err = _cmApOsxOsError(err,&_cmApOsxRoot);
				goto errLabel;
			}
				
			// wait for confirmation
			waitMs = 0;
			while( 	 drp->fmtChangeDevId != drp->devId 
        && 	drp->fmtChangeStreamId != streamIdArray[i] 
        && 	waitMs < drp->timeOutMs )
			{
				const unsigned waitIncrMs = 20;
				usleep(waitIncrMs*1000);
				waitMs += waitIncrMs;
			}
			
			// wait timed out
			if( waitMs >= drp->timeOutMs )
			{
				err = _cmApOsxOsError(kAudioHardwareUnspecifiedError,&_cmApOsxRoot);
				goto errLabel;
			}
			else
			{
        // read back the format to be really sure it changed
        if((err = AudioDeviceGetProperty( drp->devId, i, inputFl, kAudioDevicePropertyStreamFormat, &byteCnt, &sdr )) != noErr )
        {
           _cmApOsxOsError(err,&_cmApOsxRoot);
           goto errLabel;
        }
			    	 
				assert( sdr.mSampleRate == newSampleRate );		    	
			}			
		}
		
	}
	
 errLabel:
  cmMemPtrFree(&streamIdArray);
  
		return err;
}

OSStatus _cmApOsxSetFramesPerCycle( cmApOsxDevRecd* drp, Boolean inputFl, unsigned newFramesPerCycle )
{
  OSStatus       err               = noErr;
  AudioTimeStamp when;
  UInt32         ch                = 0;
  UInt32         devPropId         = kAudioDevicePropertyBufferFrameSize;
  UInt32         outByteCnt        = sizeof(UInt32);
  UInt32         curFramesPerCycle = 0;
	
	// set time stamp to zero to force the change to be immediate
	when.mHostTime = 0;
	when.mFlags = kAudioTimeStampHostTimeValid;

	// get the cur value off the param. to change
	if((err = AudioDeviceGetProperty( drp->devId, ch, inputFl, devPropId, &outByteCnt, &curFramesPerCycle )) != noErr )
		return _cmApOsxOsError(err,&_cmApOsxRoot);

	// if the cur value is the same as the new value then there is nothing to do
	if( curFramesPerCycle == newFramesPerCycle )
		return noErr;

	// attempt to set the new value	
	if((err = AudioDeviceSetProperty( drp->devId, &when, ch, inputFl, devPropId, sizeof(newFramesPerCycle), &newFramesPerCycle)) != noErr )
		return _cmApOsxOsError(err,&_cmApOsxRoot);
	
	// wait for the value to actually change
	unsigned waitMs = 0;
	while( waitMs < drp->timeOutMs )
	{
		const unsigned waitIncrMs = 20;		
		usleep(waitIncrMs*1000);
		
		// read the parameter value
    if((err = AudioDeviceGetProperty( drp->devId, ch, inputFl, devPropId, &outByteCnt, &curFramesPerCycle )) != noErr )
      return _cmApOsxOsError(err,&_cmApOsxRoot);

		// if the parameter value equals the new value then the change has taken effect	
		if( curFramesPerCycle == newFramesPerCycle )
			break;
		
		waitMs += waitIncrMs;
	}
			
	// wait timed out
	if( waitMs >= drp->timeOutMs )
		return _cmApOsxOsError(kAudioHardwareUnspecifiedError,&_cmApOsxRoot);
	
	return noErr;
}


// Note: *bufArrayPtr must be deallocated by the caller.
  cmApRC_t _cmApOsxGetBufferCfg(unsigned devIdx, AudioDeviceID devId, Boolean inputFl, cmApOsxDevRecd* drp )
{
  cmApRC_t           rc            = kOkApRC;
	OSStatus         err           = noErr;
	UInt32           byteCnt       = 0;
	Boolean          canWriteFl    = 0;
  AudioBufferList* ablp          = NULL;	
  unsigned         streamCnt     = 0;
  AudioStreamID*   streamIdArray = NULL;
  unsigned         i             = 0;
  unsigned         chIdx         = 0;
   
	// get the size of stream cfg buffer
	if( (err = AudioDeviceGetPropertyInfo(devId,0,inputFl,kAudioDevicePropertyStreamConfiguration,&byteCnt,&canWriteFl)) != noErr )
		return _cmApOsxOsError(err,&_cmApOsxRoot);

	// allocate memory to hold the AudioBufferList
	ablp = (AudioBufferList*)cmMemMallocZ(byteCnt);		
		
	// get the audio buffer list array
	if((err = AudioDeviceGetProperty(devId,0,inputFl,kAudioDevicePropertyStreamConfiguration,&byteCnt,ablp)) != noErr )
	{
    rc = _cmApOsxOsError(err,&_cmApOsxRoot);
    goto errLabel;		
	}

  // allocate a stream id array
  if((err = _cmApOsxAllocStreamIdArray( devId, inputFl, &streamIdArray, &streamCnt )) != noErr )
  {
    rc = _cmApOsxOsError(err,&_cmApOsxRoot);
    goto errLabel;
  }

  // the number of buffers and the number of frames must be the same
	assert( streamCnt == ablp->mNumberBuffers);

  cmApAudioPacket_t* pktArray = cmMemAllocZ(cmApAudioPacket_t,ablp->mNumberBuffers);

  if( inputFl )
  {
    drp->inPktCnt   = ablp->mNumberBuffers; 
    drp->inPktArray = pktArray;
  }
  else
  {
    drp->outPktCnt   = ablp->mNumberBuffers;
    drp->outPktArray = pktArray;
  }


  for(i=0; i<ablp->mNumberBuffers; ++i)
  {
		AudioStreamBasicDescription sdr;
		
    // get the size of the stream desc recd
    if((err = AudioDeviceGetPropertyInfo( devId, i, inputFl, kAudioDevicePropertyStreamFormat, &byteCnt, &canWriteFl )) != noErr )
    {
      _cmApOsxOsError(err,&_cmApOsxRoot);
      goto errLabel;
		}

    assert( byteCnt == sizeof(sdr) );
	
    // get the stream desc recd
    if((err = AudioDeviceGetProperty( devId, i, inputFl, kAudioDevicePropertyStreamFormat, &byteCnt, &sdr )) != noErr )
    {
      _cmApOsxOsError(err,&_cmApOsxRoot);
      goto errLabel;
    }

    // assert that the data format is packed float32 native endian
    //assert( IsAudioFormatNativeEndian(sdr) );

    // 0x6c70636d = lpcm 
    //printf("%s %i dev:%i sr:%f fmtId:0x%lx fmtFl:0x%lx bytesPerPkt:%li frmsPerPkt:%li bytesPerFrm:%li chsPerFrm:%li bitsPerCh:%li\n",
    //  inputFl?"i":"o",i,devIdx,sdr.mSampleRate,sdr.mFormatID,sdr.mFormatFlags,sdr.mBytesPerPacket,sdr.mFramesPerPacket,sdr.mBytesPerFrame,sdr.mChannelsPerFrame,sdr.mBitsPerChannel);


    // assert that all buffers have the sample rate of the device
    if( drp->srate == 0 )
      drp->srate = sdr.mSampleRate;

    assert( drp->srate == sdr.mSampleRate );

    // install a stream property listener
		AudioStreamAddPropertyListener( streamIdArray[i], 
										kAudioPropertyWildcardChannel,
										kAudioPropertyWildcardPropertyID,
										cmApOsxSystemStreamPropertyListenerProc,
										drp );
    
    pktArray[i].devIdx         = devIdx;
    pktArray[i].begChIdx       = chIdx;
    pktArray[i].chCnt          =  ablp->mBuffers[i].mNumberChannels; //sdr.mChannelsPerFrame;
    pktArray[i].audioFramesCnt =  ablp->mBuffers[i].mDataByteSize / (sizeof(cmApSample_t) * ablp->mBuffers[i].mNumberChannels); //sdr.mFramesPerPacket;
    pktArray[i].flags          = kInterleavedApFl | kFloatApFl;
    pktArray[i].bitsPerSample  = sizeof(cmApSample_t) * 8;
    pktArray[i].audioBytesPtr  = NULL; 
    pktArray[i].userCbPtr      = NULL; // the userCbPtr value isn't availabe until the cmApOsxDeviceSetup()

    // verify that all buffers on this device have the same size
    assert(i==0 || pktArray[i].audioFramesCnt == pktArray[i-1].audioFramesCnt );


    chIdx +=  ablp->mBuffers[i].mNumberChannels; //sdr.mChannelsPerFrame;

    if( inputFl )
    {
      // track the total number of input channels on this device
      drp->inChCnt = chIdx;

      // track the frames per cycle on this device
      if( drp->inFramesPerCycle == 0 )
        drp->inFramesPerCycle = pktArray[i].audioFramesCnt;

      
      assert( drp->inFramesPerCycle == pktArray[i].audioFramesCnt );
    }
    else
    {
      drp->outChCnt = chIdx;

      if( drp->outFramesPerCycle == 0 )
        drp->outFramesPerCycle = pktArray[i].audioFramesCnt;

      assert( drp->outFramesPerCycle == pktArray[i].audioFramesCnt );

    }
  }
  
 errLabel:
  cmMemPtrFree(&streamIdArray);
  cmMemPtrFree(&ablp);
  return rc; 
}


OSStatus _cmApOsxSystemDeviceIOProc(	AudioDeviceID			inDevice,
													const AudioTimeStamp*	  inNow,
													const AudioBufferList*	iabl,
													const AudioTimeStamp*	  inInputTime,
													AudioBufferList*		    oabl, 
													const AudioTimeStamp*	  inOutputTime,
													void*					          inClientData)
{
 
	cmApOsxDevRecd*       drp    = (cmApOsxDevRecd*)inClientData;

  if( iabl->mNumberBuffers!=0 && iabl->mNumberBuffers != drp->inPktCnt )
    return noErr;

  if( oabl->mNumberBuffers!=0 && oabl->mNumberBuffers != drp->outPktCnt  )
    return noErr;

  //assert( iabl->mNumberBuffers==0 || iabl->mNumberBuffers == drp->inPktCnt );
  //assert( oabl->mNumberBuffers==0 || oabl->mNumberBuffers == drp->outPktCnt );

  // setup the incoming packets (ADC->app)
  AudioBuffer*       bp = iabl->mBuffers;
  AudioBuffer*       ep = bp + iabl->mNumberBuffers;
  cmApAudioPacket_t* pp = drp->inPktArray;
  unsigned           chIdx = 0;

  for(; bp<ep; ++bp,++pp)
  {
    pp->audioBytesPtr  = (float*)bp->mData;
    pp->audioFramesCnt =  bp->mDataByteSize / (bp->mNumberChannels * sizeof(cmApSample_t));
    pp->begChIdx       = chIdx;
    pp->chCnt          = bp->mNumberChannels;
    chIdx             += pp->chCnt;
  }

  // setup the outgoing packets (app->DAC)
  bp = oabl->mBuffers;
  ep = bp + oabl->mNumberBuffers;
  pp = drp->outPktArray;

  for(chIdx=0; bp<ep; ++bp,++pp)
  {
    pp->audioBytesPtr  = (float*)bp->mData;
    pp->audioFramesCnt =  bp->mDataByteSize / (bp->mNumberChannels * sizeof(cmApSample_t));
    pp->begChIdx       = chIdx;
    pp->chCnt          = bp->mNumberChannels;
    chIdx             += pp->chCnt;
  }

  // call the app
  drp->callbackPtr( 
    iabl->mNumberBuffers ? drp->inPktArray  : NULL, iabl->mNumberBuffers,
    oabl->mNumberBuffers ? drp->outPktArray : NULL, oabl->mNumberBuffers );
 
   return noErr;
}


cmApRC_t      cmApOsxInitialize( cmRpt_t* rpt )
{
  cmApRC_t       rc            = kOkApRC;
  Boolean        outWritableFl = false;
  UInt32         outByteCnt    = 0;
  OSStatus       err           = noErr;
  AudioDeviceID* devIdArray    = NULL;
  unsigned       i             = 0;
  cmApOsxDevRecd*   devArray   = NULL;

  if((rc = cmApOsxFinalize()) != kOkApRC )
    return rc;

  _cmApOsxRoot.rpt      = rpt;
  _cmApOsxRoot.devArray = NULL;
	
  // get the size of the device id array
  if((err = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices, &outByteCnt, &outWritableFl )) != noErr )
    return _cmApOsxOsError(err,&_cmApOsxRoot);
		
  assert( outByteCnt > 0 );
  
  // calc. the device count
  _cmApOsxRoot.devCnt  = outByteCnt / sizeof(AudioDeviceID);

  assert( _cmApOsxRoot.devCnt*sizeof(AudioDeviceID) == outByteCnt );
		
  // allocate space for the device id array and the device array
  devIdArray       = cmMemAllocZ( AudioDeviceID, _cmApOsxRoot.devCnt );	
  devArray         = cmMemAllocZ( cmApOsxDevRecd,     _cmApOsxRoot.devCnt );
  _cmApOsxRoot.devArray = devArray;

  // get the device id array
  if((err = AudioHardwareGetProperty( kAudioHardwarePropertyDevices, &outByteCnt, devIdArray )) != noErr )
  {
    rc = _cmApOsxOsError(err,&_cmApOsxRoot);
    goto errLabel;
  }

  // for each 
  for(i=0; i<_cmApOsxRoot.devCnt; ++i)
  {
    // device name
    if((err = _cmApOsxAllocDeviceCFString(devIdArray[i], 0, false, kAudioDevicePropertyDeviceNameCFString, &devArray[i].nameStr  )) != noErr )
    {
      rc = _cmApOsxOsError(err,&_cmApOsxRoot);
      goto errLabel;
    }

    // device mfg
    if((err = _cmApOsxAllocDeviceCFString(devIdArray[i], 0, false, kAudioDevicePropertyDeviceManufacturerCFString, &devArray[i].mfgStr  )) != noErr )
    {
      rc = _cmApOsxOsError(err,&_cmApOsxRoot);
      goto errLabel;
    }

    // in buf array
    if((rc = _cmApOsxGetBufferCfg(i,devIdArray[i], true, devArray + i )) != kOkApRC )
      goto errLabel;

    // out buf array
    if((rc = _cmApOsxGetBufferCfg(i,devIdArray[i], false, devArray + i)) != kOkApRC )
      goto errLabel;


    devArray[i].devId           = devIdArray[i];
    devArray[i].devIdx          = i;
    devArray[i].callbackPtr     = NULL;
    devArray[i].timeOutMs       = 1000;
    devArray[i].ioProcId        = NULL;
  }
  
 errLabel:

  if( devIdArray != NULL )
    cmMemFree(devIdArray);

  if( rc == kOkApRC )
    return rc;


  cmApOsxFinalize();
  return rc;
}

cmApRC_t      cmApOsxFinalize()
{
  cmApOsxDevRecd* devArray = _cmApOsxRoot.devArray;
  unsigned   i        = 0;
  OSStatus   err;

  for(i=0; i<_cmApOsxRoot.devCnt; ++i)
  {
    unsigned       j;

    for(j=0; j<2; ++j)
    { 
      AudioStreamID* streamIdArray;
      unsigned       streamCnt;
      unsigned       k;

      if((err = _cmApOsxAllocStreamIdArray(devArray[i].devId, true,&streamIdArray,&streamCnt )) != noErr )
        _cmApOsxOsError(err,&_cmApOsxRoot);

      for(k=0; k<streamCnt; ++k)
        if((err = AudioStreamRemovePropertyListener( streamIdArray[k], 
              kAudioPropertyWildcardChannel,
              kAudioPropertyWildcardPropertyID,
              cmApOsxSystemStreamPropertyListenerProc)) != noErr )
          _cmApOsxOsError(err,&_cmApOsxRoot);

      cmMemPtrFree(&streamIdArray);
        
    }  

		//if((err = AudioDeviceRemoveIOProc( devArray[i].devId, _cmApOsxSystemDeviceIOProc )) != noErr )
    if( devArray[i].ioProcId != NULL )
      if((err = AudioDeviceDestroyIOProcID( devArray[i].devId, devArray[i].ioProcId )) != noErr )
        _cmApOsxOsError(err,&_cmApOsxRoot);

    if( devArray[i].nameStr != NULL )
      cmMemFree(devArray[i].nameStr);

    if( devArray[i].mfgStr != NULL )
      cmMemFree(devArray[i].mfgStr);

    if( devArray[i].inPktArray != NULL )
      cmMemFree( devArray[i].inPktArray );

    if( devArray[i].outPktArray!= NULL )
      cmMemFree( devArray[i].outPktArray );  
  }

  if( devArray != NULL )
  {
    cmMemFree(devArray);
    _cmApOsxRoot.devArray = NULL;
    _cmApOsxRoot.devCnt   = 0;
  }

  return kOkApRC;
}

cmApRC_t      cmApOsxDeviceCount()
{ return _cmApOsxRoot.devCnt; }

const char* cmApOsxDeviceLabel( unsigned devIdx )
{
  assert( devIdx < _cmApOsxRoot.devCnt );
  return _cmApOsxRoot.devArray[ devIdx ].nameStr;
}

unsigned    cmApOsxDeviceChannelCount( unsigned devIdx, bool inputFl )
{
  assert( devIdx < _cmApOsxRoot.devCnt );
  return inputFl ? _cmApOsxRoot.devArray[ devIdx ].inChCnt : _cmApOsxRoot.devArray[ devIdx ].outChCnt;
}

double      cmApOsxDeviceSampleRate( unsigned devIdx )
{
  assert( devIdx < _cmApOsxRoot.devCnt );
  return _cmApOsxRoot.devArray[ devIdx ].srate;
}

unsigned    cmApOsxDeviceFramesPerCycle( unsigned devIdx, bool inputFl )
{
  assert( devIdx < _cmApOsxRoot.devCnt );
  return inputFl ? _cmApOsxRoot.devArray[ devIdx ].inFramesPerCycle : _cmApOsxRoot.devArray[ devIdx ].outFramesPerCycle;
}

cmApRC_t      cmApOsxDeviceSetup(          
  unsigned          devIdx, 
  double            srate, 
  unsigned          framesPerCycle, 
  cmApCallbackPtr_t callbackPtr,
  void*             userCbPtr )
{
  assert( devIdx < _cmApOsxRoot.devCnt );

  //cmApRC_t     rc  = kOkApRC;
  OSStatus   err;
  cmApOsxDevRecd* drp = _cmApOsxRoot.devArray + devIdx;
  unsigned   j;
  
  if( cmApOsxDeviceIsStarted(devIdx) )
    cmApOsxDeviceStop(devIdx);

  // set the sample rate
  if( drp->srate != srate )
  {
    for(j=0; j<2; ++j )
      if((err = _cmApOsxSetSampleRate(drp, j==0, srate )) != noErr )
        goto errLabel;

    drp->srate = srate;
  }

  // set the frames per cycle
  for(j=0; j<2; ++j)
  {
    unsigned* fpcPtr = j==0 ? &drp->inFramesPerCycle : &drp->outFramesPerCycle;

    if( framesPerCycle !=  (*fpcPtr) )
    {
      if((err = _cmApOsxSetFramesPerCycle(drp, j==0, framesPerCycle )) == noErr )
        *fpcPtr = framesPerCycle;
      else
        goto errLabel;
    }  
  }

  // set the user callback data ptr in each packet on this device
  for(j=0; j<2; ++j)
  {
    unsigned         k;
    cmApAudioPacket_t* packetArray = j==0 ? drp->inPktArray : drp->outPktArray;
    unsigned           pktCnt      = j==0 ? drp->inPktCnt   : drp->outPktCnt;

    for(k=0; k<pktCnt; ++k)
      packetArray[k].userCbPtr = userCbPtr;
  }

  drp->callbackPtr = callbackPtr;

  // if the io 
  if( drp->ioProcId != NULL )
    if((err = AudioDeviceDestroyIOProcID( drp->devId, drp->ioProcId )) != noErr )
      _cmApOsxOsError(err,&_cmApOsxRoot);


  // set the io proc 			
  drp->ioProcId = NULL;
  //if( (err = AudioDeviceAddIOProc(drp->devId,_cmApOsxSystemDeviceIOProc,(void*)drp) ) != noErr )
  if( (err = AudioDeviceCreateIOProcID(drp->devId,_cmApOsxSystemDeviceIOProc,(void*)drp,&drp->ioProcId) ) != noErr )
  {
    _cmApOsxOsError(err,&_cmApOsxRoot);
    return kSysErrApRC;
  }	

  return kOkApRC;

 errLabel:

  return kSysErrApRC;
}


cmApRC_t      cmApOsxDeviceStart( unsigned devIdx )
{
  assert( devIdx < _cmApOsxRoot.devCnt );
  OSStatus err;

  //if( (err = AudioDeviceStart(_cmApOsxRoot.devArray[devIdx].devId,_cmApOsxSystemDeviceIOProc)) != noErr )
  if( (err = AudioDeviceStart(_cmApOsxRoot.devArray[devIdx].devId,_cmApOsxRoot.devArray[devIdx].ioProcId)) != noErr )
	{
		_cmApOsxOsError(err,&_cmApOsxRoot);	
		return kSysErrApRC;
	}

  return kOkApRC;
}

cmApRC_t      cmApOsxDeviceStop( unsigned devIdx )
{
  assert( devIdx < _cmApOsxRoot.devCnt );

	OSStatus err;
		 
  //if( (err = AudioDeviceStop(_cmApOsxRoot.devArray[devIdx].devId,_cmApOsxSystemDeviceIOProc)) != noErr )
  if( (err = AudioDeviceStop(_cmApOsxRoot.devArray[devIdx].devId,_cmApOsxRoot.devArray[devIdx].ioProcId)) != noErr )
	{
		_cmApOsxOsError(err,&_cmApOsxRoot);	
		return kSysErrApRC;
	}

  return kOkApRC;
}

bool        cmApOsxDeviceIsStarted( unsigned devIdx )
{
  assert( devIdx < _cmApOsxRoot.devCnt );
  return false;
}
/*
void apReport( apPrintFunc_t printFunc )
{
  unsigned i;
  for(i=0; i<_cmApOsxRoot.devCnt; ++i)
  {
    cmApOsxDevRecd* drp = _cmApOsxRoot.devArray + i;
    printf("%i in:%i out:%i %s %s\n",i,drp->inChCnt,drp->outChCnt,drp->nameStr,drp->mfgStr);
  }
}
*/