//| Copyright: (C) 2020-2024 Kevin Larke <contact AT larke DOT org> 
//| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
#include "cwCommon.h"
#include "cwLog.h"
#include "cwCommonImpl.h"
#include "cwTest.h"
#include "cwMem.h"
#include "cwFile.h"
#include "cwObject.h"
#include "cwAudioFile.h"
#include "cwMath.h"
#include "cwFileSys.h"

// #define _24to32_aif( p ) ((int)( ((p[0]>127?255:0) << 24) + (((int)p[0]) << 16) +  (((int)p[1]) <<8) + p[2]))  // no-swap equivalent
// See note in:_cmAudioFileReadFileHdr()
// Note that this code byte swaps as it converts - this is to counter the byte swap that occurs in cmAudioFileReadInt().
#define _24to32_aif( p ) ((int)( ((p[0]>127?255:0) <<  0) + (((int)p[2]) << 24) +  (((int)p[1]) <<16) + (((int)p[0]) << 8)))

#define _24to32_wav( p ) ((int)( ((p[2]>127?255:0) << 24) + (((int)p[2]) << 16) +  (((int)p[1]) <<8) + p[0]))

#define _cmAfSwap16(v)  cwSwap16(v)
#define _cmAfSwap32(v)  cwSwap32(v)

#ifdef cmBIG_ENDIAN
#define _cmAifSwapFl    (0)
#define _cmWavSwapFl    (1)
#else
#define _cmAifSwapFl    (1)
#define _cmWavSwapFl    (0)
#endif

namespace cw
{
  namespace audiofile
  {
    enum
    {
     kAiffFileId   = 'FORM',
     kAiffChkId    = 'AIFF',
     kAifcChkId    = 'AIFC',
     kSowtCompId   = 'sowt',
     kNoneCompId   = 'NONE',

     kWavFileId    = 'FFIR',
     kWavChkId     = 'EVAW',
    };

    enum
    {
      kWriteAudioGutsFl = 0x01,
      kWriteFloatFl     = 0x02
    };

    enum
    {
      kIntegerFmtId = 1,
      kFloatFmtId   = 3,
      kBitsPerByte  = 8,
      
      kMax24Bits   = 0x007fffff,
      kMax16Bits   = 0x00007fff,
      kMax32Bits   = 0x7fffffff
    }; 

    typedef struct audiofile_str
    {
      FILE*          fp;
      info_t         info;        // audio file details 
      unsigned       curFrmIdx;   // current frame offset 
      unsigned       fileByteCnt; // file byte cnt 
      unsigned       smpByteOffs; // byte offset of the first sample
      marker_t*      markArray;
      unsigned       flags;
      char*          fn;
    } af_t;

    rc_t _writeHdr( af_t* p );

    af_t* _handleToPtr( handle_t h )
    {  return handleToPtr<handle_t,af_t>(h); }
    
    rc_t _destroy( af_t* p )
    {
      rc_t rc = kOkRC;
      
      if( p != nullptr )
      {

        if( cwIsFlag(p->flags, kWriteAudioGutsFl ) )
          if((rc = _writeHdr(p)) != kOkRC )
            return rc;
        
        if( p->fp != nullptr )
        {
          fclose(p->fp);
          p->fp = nullptr;
        }

        mem::release(p->fn);
        mem::release(p);
        
      }
      
      return rc;
    }

    rc_t _seek( af_t* p, long byteOffset, int origin )
    {
      if( fseek(p->fp,byteOffset,origin) != 0 )
        return cwLogError(kSeekFailRC,"Audio file seek failed on '%s'.",cwStringNullGuard(p->fn));
      return kOkRC;
    }

    rc_t _read( af_t* p, void* eleBuf, unsigned bytesPerEle, unsigned eleCnt )
    {
      if( fread(eleBuf,bytesPerEle,eleCnt,p->fp) != eleCnt )
        return cwLogError(kReadFailRC,"Audio file read failed on '%s'.",cwStringNullGuard(p->fn));
    
      return kOkRC;
    }

    rc_t _readUInt32( af_t* p, std::uint32_t* valuePtr )
    {
      rc_t rc;

      if(( rc = _read(p, valuePtr, sizeof(*valuePtr), 1 )) != kOkRC )
        return rc;
    
      if( cwIsFlag(p->info.flags,kSwapAfFl) )
        *valuePtr = _cmAfSwap32(*valuePtr);

      return rc;
    } 


    rc_t _readUInt16( af_t* p, std::uint16_t* valuePtr )
    {
      rc_t rc;

      if(( rc = _read(p, valuePtr, sizeof(*valuePtr), 1 )) != kOkRC )
        return rc;
    
      if( cwIsFlag(p->info.flags,kSwapAfFl) )
        *valuePtr = _cmAfSwap16(*valuePtr);

      return rc;
    } 

    rc_t _readPascalString( af_t* p, char s[kAudioFileLabelCharCnt] )
    {
      rc_t rc;
      unsigned char n;

      if((rc = _read(p,&n,sizeof(n),1)) != kOkRC )
        return rc;

      if((rc = _read(p,s,n,1)) != kOkRC )
        return rc;

      s[n] = '\0';

      if( n % 2 == 0 )
        rc = _seek(p,1,SEEK_CUR);

      return rc;
    }

    rc_t _readString( af_t* p, char* s, unsigned sn )
    {
      rc_t rc;
      if((rc = _read(p,s,sn,1)) != kOkRC )
        return rc;

      return kOkRC;
    }

    rc_t _readX80( af_t* p, double* x80Ptr )
    {
      unsigned char s[10];
      rc_t rc = kOkRC;

      if((rc = _read(p,s,10,1)) != kOkRC )
        return rc;

      *x80Ptr = math::x80ToDouble(s);
      return kOkRC;
    }

    rc_t _readChunkHdr( af_t* p, std::uint32_t* chkIdPtr, unsigned* chkByteCntPtr )
    {
      rc_t rc      = kOkRC;

      *chkIdPtr      = 0;
      *chkByteCntPtr = 0;

      if((rc = _readUInt32(p,chkIdPtr)) != kOkRC )
        return rc;

      if((rc = _readUInt32(p,chkByteCntPtr)) != kOkRC )
        return rc;

      // the actual on disk chunk size is always incrmented up to the next even integer
      *chkByteCntPtr += (*chkByteCntPtr) % 2;

      return rc;
    }

    rc_t _readFileHdr( af_t* p, unsigned constFormId, unsigned constAifId, bool swapFl )
    {
      rc_t     rc         = kOkRC;
      std::uint32_t formId     = 0;
      std::uint32_t aifId      = 0;
      unsigned   chkByteCnt = 0;

      p->info.flags     = 0;
      p->curFrmIdx      = 0;
      p->fileByteCnt    = 0;

      if((rc = _seek(p,0,SEEK_SET)) != kOkRC ) 
        return rc;

      // set the swap flags
      p->info.flags = cwEnaFlag(p->info.flags,kSwapAfFl,       swapFl);
      p->info.flags = cwEnaFlag(p->info.flags,kSwapSamplesAfFl,swapFl);

      if((rc = _readChunkHdr(p,&formId,&p->fileByteCnt)) != kOkRC )
        return rc;

      //
      // use -Wno-multichar on GCC cmd line to  disable the multi-char warning 
      //


      // check the FORM/RIFF id
      if( formId != constFormId )
        return kSyntaxErrorRC;

      // read the AIFF/WAVE id
      if((rc = _readChunkHdr(p,&aifId,&chkByteCnt)) != kOkRC )
        return rc;

      // check for the AIFC 
      if( formId == kAiffFileId && aifId != constAifId )
      {
        if( aifId == kAifcChkId )
          p->info.flags = cwSetFlag(p->info.flags,kAifcAfFl);
        else
          return kInvalidDataTypeRC;
      }

      // set the audio file type flag 
      if( aifId==kAiffChkId || aifId==kAifcChkId )
        p->info.flags = cwSetFlag(p->info.flags,kAiffAfFl);
  
      if( aifId==kWavChkId )
        p->info.flags = cwSetFlag(p->info.flags,kWavAfFl);
    

      return rc;
    }

    rc_t _readCommChunk( af_t* p )
    {
      rc_t rc = kOkRC;
      std::uint16_t ui16;
      std::uint32_t ui32;

      if((rc = _readUInt16(p,&ui16)) != kOkRC )
        return rc;
      p->info.chCnt = ui16;

      if((rc = _readUInt32(p,&ui32)) != kOkRC )
        return rc;
      p->info.frameCnt = ui32;

      if((rc = _readUInt16(p,&ui16)) != kOkRC )
        return rc;
      p->info.bits = ui16;

      if((rc = _readX80(p,&p->info.srate)) != kOkRC )
        return rc;

      // if this is an AIFC format file  ...
      if( cwIsFlag(p->info.flags,kAifcAfFl) )
      {
        if((rc = _readUInt32(p,&ui32))  != kOkRC )
          return rc;

        switch( ui32 )
        {
          case kNoneCompId:
            break;

          case kSowtCompId:
            // If the compression type is set to 'swot' 
            // then the samples are written in little-endian (Intel) format
            // rather than the default big-endian format. 
            p->info.flags = cwTogFlag(p->info.flags,kSwapSamplesAfFl);
            break;

          default:
            rc = cwLogError(kSyntaxErrorRC,"Unknown AIFC type id: 0x%x in '%s'.",ui32,cwStringNullGuard(p->fn) );
        }
      }

      return rc;
    }

    rc_t _readSsndChunk( af_t* p )
    {
      rc_t rc = kOkRC;

      std::uint32_t smpOffs=0, smpBlkSize=0;
  
      if((rc = _readUInt32(p,&smpOffs)) != kOkRC )
        return rc;

      if((rc = _readUInt32(p,&smpBlkSize)) != kOkRC )
        return rc;
  
      if((rc = _seek(p,smpOffs, SEEK_CUR)) != kOkRC )
        return rc;

      p->smpByteOffs = ftell(p->fp);

      return rc;
    }

    rc_t _readMarkerChunk( af_t* p )
    {
      rc_t rc = kOkRC;

      std::uint16_t ui16;
      std::uint32_t ui32;
      unsigned   i;

      if((rc = _readUInt16(p,&ui16)) != kOkRC )
        return rc;

      p->info.markerCnt = ui16;

      assert(p->markArray == NULL);

      marker_t* m = mem::allocZ<marker_t>(p->info.markerCnt);

      p->info.markerArray = m; 

      for(i=0; i<p->info.markerCnt; ++i)
      {
        if((rc = _readUInt16(p,&ui16)) != kOkRC )
          return rc;

        m[i].id = ui16;

        if((rc = _readUInt32(p,&ui32)) != kOkRC )
          return rc;

        m[i].frameIdx = ui32;

        if((rc = _readPascalString(p,m[i].label)) != kOkRC )
          return rc;
    
      }
      return rc;
    }

    rc_t _readFmtChunk( af_t* p )
    {
      rc_t rc = kOkRC;
      unsigned short fmtId, chCnt, blockAlign, bits;
      unsigned srate, bytesPerSec;

      if((rc = _readUInt16(p,&fmtId)) != kOkRC )
        return rc;
  
      if((rc = _readUInt16(p,&chCnt)) != kOkRC )
        return rc;

      if((rc = _readUInt32(p,&srate)) != kOkRC )
        return rc;
  
      if((rc = _readUInt32(p,&bytesPerSec)) != kOkRC )
        return rc;

      if((rc = _readUInt16(p,&blockAlign)) != kOkRC )
        return rc;
  
      if((rc = _readUInt16(p,&bits)) != kOkRC )
        return rc;

      p->info.chCnt = chCnt;
      p->info.bits  = bits;
      p->info.srate = srate;
      p->flags      = fmtId == kFloatFmtId ? kWriteFloatFl : 0;

      // if the 'data' chunk was read before the 'fmt' chunk then info.frameCnt 
      // holds the number of bytes in the data chunk
      if( p->info.frameCnt != 0 )
        p->info.frameCnt = p->info.frameCnt / (p->info.chCnt * (p->info.bits/kBitsPerByte));

      return rc;
    }

    rc_t _readDatcmhunk( af_t* p, unsigned chkByteCnt )
    {
      // if the 'fmt' chunk was read before the 'data' chunk then info.chCnt is non-zero
      if( p->info.chCnt != 0 )
        p->info.frameCnt = chkByteCnt / (p->info.chCnt * (p->info.bits/kBitsPerByte));
      else
        p->info.frameCnt = chkByteCnt;

      p->smpByteOffs = ftell(p->fp);

      return kOkRC;
    }

    rc_t _readBextChunk( af_t* p)
    {
      rc_t rc = kOkRC;

      if((rc = _readString(p,p->info.bextRecd.desc,kAfBextDescN)) != kOkRC )
        return rc;

      if((rc = _readString(p,p->info.bextRecd.origin,kAfBextOriginN)) != kOkRC )
        return rc;

      if((rc = _readString(p,p->info.bextRecd.originRef,kAfBextOriginRefN)) != kOkRC )
        return rc;

      if((rc = _readString(p,p->info.bextRecd.originDate,kAfBextOriginDateN)) != kOkRC )
        return rc;

      if((rc = _readString(p,p->info.bextRecd.originTime,kAfBextOriginTimeN)) != kOkRC )
        return rc;

      if((rc = _readUInt32(p,&p->info.bextRecd.timeRefLow)) != kOkRC )
        return rc;

      if((rc = _readUInt32(p,&p->info.bextRecd.timeRefHigh)) != kOkRC )
        return rc;

      return rc;
    }

    rc_t _open( af_t* p, const char* fname, const char* fileModeStr )
    {
      rc_t rc = kOkRC;
      char* fn = nullptr;
      
      // zero the info record
      memset(&p->info,0,sizeof(p->info));

      // expand the path
      if((fn = filesys::expandPath(fname)) == nullptr )
      {
        rc = cwLogError(kOpFailRC,"File path expansion failed on '%s'.",cwStringNullGuard(fname));
        goto errLabel;
      }

      // open the file
      if((p->fp = fopen(fn,fileModeStr)) == NULL )
      {
        rc = cwLogError(kOpenFailRC,"Audio file open failed on '%s'.",cwStringNullGuard(fname));
        goto errLabel;
      }

      // read the file header
      if((rc = _readFileHdr(p,kAiffFileId,kAiffChkId,_cmAifSwapFl)) != kOkRC )
        if((rc = _readFileHdr(p,kWavFileId,kWavChkId,_cmWavSwapFl)) != kOkRC )
          goto errLabel;
  
      // seek past the file header
      if((rc = _seek(p,12,SEEK_SET)) != kOkRC )
        goto errLabel;

      // zero chCnt and frameCnt to allow the order of the 'data' and 'fmt' chunks to be noticed
      p->info.chCnt    = 0;
      p->info.frameCnt = 0;

      while( ftell(p->fp ) < p->fileByteCnt )
      {
        unsigned chkId, chkByteCnt;
        if((rc = _readChunkHdr(p,&chkId,&chkByteCnt)) != kOkRC )
          goto errLabel;

        unsigned offs = ftell(p->fp);

        if( cwIsFlag(p->info.flags,kAiffAfFl) )
          switch(chkId)
          {
            case 'COMM':
              if((rc = _readCommChunk(p)) != kOkRC )
                goto errLabel;
              break;

            case 'SSND':
              if((rc = _readSsndChunk(p)) != kOkRC )
                goto errLabel;
              break;

            case 'MARK':
              if((rc = _readMarkerChunk(p)) != kOkRC )
                goto errLabel;
              break;
          }
        else
          switch(chkId)
          {
            case ' tmf':
              if((rc = _readFmtChunk(p)) != kOkRC )
                goto errLabel;
              break;

            case 'atad':
              if((rc = _readDatcmhunk(p,chkByteCnt)) != kOkRC )
                goto errLabel;
              break;

            case 'txeb':
              if((rc = _readBextChunk(p)) != kOkRC )
                goto errLabel;
              break;
          }


        // seek to the end of this chunk
        if((rc = _seek(p,offs+chkByteCnt,SEEK_SET)) != kOkRC )
          goto errLabel;
      }

    errLabel:
      if( rc!=kOkRC )
        _destroy(p);

      mem::release(fn);

      return rc;
    }

    rc_t   _writeBytes( af_t* p, const void* b, unsigned bn )
    {
      rc_t rc = kOkRC;
      if( fwrite( b, bn, 1, p->fp ) != 1 )
        return cwLogError(kWriteFailRC,"Audio file write failed on '%s'.",cwStringNullGuard(p->fn));

      return rc;
    }

    rc_t _writeId( af_t* p, const char* s )
    {  return _writeBytes( p,  s, strlen(s)) ; }

    rc_t _writeUInt32( af_t* p, unsigned v )
    {
      if( cwIsFlag(p->info.flags,kSwapAfFl) )
        v = _cmAfSwap32(v);
  
      return _writeBytes( p, &v, sizeof(v)) ; 
    }

    rc_t _writeUInt16( af_t* p, unsigned short v )
    {
      if( cwIsFlag(p->info.flags,kSwapAfFl) )
        v = _cmAfSwap16(v);
  
      return _writeBytes( p, &v, sizeof(v)) ; 
    }

    rc_t _writeAiffHdr( af_t* p )
    {
      rc_t        rc = kOkRC;
      unsigned char srateX80[10];
 
      math::doubleToX80( p->info.srate, srateX80 );  

      unsigned hdrByteCnt  = 54;
      unsigned ssndByteCnt = 8 + (p->info.chCnt * p->info.frameCnt * (p->info.bits/kBitsPerByte));
      unsigned formByteCnt = hdrByteCnt + ssndByteCnt - 8;
      unsigned commByteCnt = 18;
      unsigned ssndSmpOffs = 0;
      unsigned ssndBlkSize = 0;

      // if ssndByteCnt is odd
      if( ssndByteCnt % 2 == 1 )
      {
        formByteCnt++;
      }

      if(( rc = _seek( p, 0, SEEK_SET )) != kOkRC )
        return rc;

      if(( rc = _writeId(     p, "FORM"))           != kOkRC ) return rc;
      if(( rc = _writeUInt32( p, formByteCnt))      != kOkRC ) return rc;
      if(( rc = _writeId(     p, "AIFF"))           != kOkRC ) return rc;
      if(( rc = _writeId(     p, "COMM"))           != kOkRC ) return rc;
      if(( rc = _writeUInt32( p, commByteCnt))      != kOkRC ) return rc;
      if(( rc = _writeUInt16( p, p->info.chCnt))    != kOkRC ) return rc;
      if(( rc = _writeUInt32( p, p->info.frameCnt)) != kOkRC ) return rc;
      if(( rc = _writeUInt16( p, p->info.bits))     != kOkRC ) return rc;
      if(( rc = _writeBytes(  p, &srateX80,10))     != kOkRC ) return rc;
      if(( rc = _writeId(     p, "SSND"))           != kOkRC ) return rc;
      if(( rc = _writeUInt32( p, ssndByteCnt))      != kOkRC ) return rc;
      if(( rc = _writeUInt32( p, ssndSmpOffs))      != kOkRC ) return rc;
      if(( rc = _writeUInt32( p, ssndBlkSize))      != kOkRC ) return rc;

      return rc;
    }

    rc_t _writeWavHdr( af_t* p )
    {
      rc_t     rc            = kOkRC;
      short    chCnt         = p->info.chCnt;
      unsigned frmCnt        = p->info.frameCnt;
      short    bits          = p->info.bits;
      unsigned srate         = p->info.srate;
      short    fmtId         = p->flags & kWriteFloatFl ? kFloatFmtId : kIntegerFmtId;
      unsigned bytesPerSmp   = bits/kBitsPerByte;
      unsigned hdrByteCnt    = 36;
      unsigned fmtByteCnt    = 16;
      unsigned blockAlignCnt = chCnt * bytesPerSmp;
      unsigned sampleCnt     = chCnt * frmCnt;
      unsigned dataByteCnt   = sampleCnt * bytesPerSmp;

      if(( rc = _seek( p, 0, SEEK_SET )) != kOkRC )
        return rc;

      if((rc = _writeId(     p, "RIFF"))                   != kOkRC ) goto errLabel;
      if((rc = _writeUInt32( p, hdrByteCnt + dataByteCnt)) != kOkRC ) goto errLabel;
      if((rc = _writeId(     p, "WAVE"))                   != kOkRC ) goto errLabel;
      if((rc = _writeId(     p, "fmt "))                   != kOkRC ) goto errLabel;
      if((rc = _writeUInt32( p, fmtByteCnt))               != kOkRC ) goto errLabel;
      if((rc = _writeUInt16( p, fmtId))                    != kOkRC ) goto errLabel;
      if((rc = _writeUInt16( p, chCnt))                    != kOkRC ) goto errLabel;
      if((rc = _writeUInt32( p, srate))                    != kOkRC ) goto errLabel;
      if((rc = _writeUInt32( p, srate * blockAlignCnt))    != kOkRC ) goto errLabel;
      if((rc = _writeUInt16( p, blockAlignCnt))            != kOkRC ) goto errLabel;
      if((rc = _writeUInt16( p, bits))                     != kOkRC ) goto errLabel;
      if((rc = _writeId(     p, "data"))                   != kOkRC ) goto errLabel;
      if((rc = _writeUInt32( p, dataByteCnt))              != kOkRC ) goto errLabel;

    errLabel:
      return rc;
    }

    rc_t _writeHdr( af_t* p )
    {
      if( cwIsFlag(p->info.flags,kWavAfFl) )
        return _writeWavHdr(p);

      return _writeAiffHdr(p);
    }

    rc_t _filenameToAudioFileType( const char* fn, unsigned& flagsRef )
    {
      rc_t rc = kOkRC;
      
      filesys::pathPart_t* pp = nullptr;

      if((pp = filesys::pathParts(fn)) == NULL )
      {
        rc = cwLogError(kInvalidArgRC,"The audio file name '%s' could not be parsed.",cwStringNullGuard(fn));
        goto errLabel;
      }

      if(pp->extStr == nullptr )
      {
        rc = cwLogError(kInvalidArgRC,"The audio file name '%s' does not have an extension.",cwStringNullGuard(fn));
        goto errLabel;
      }
      else
      {
        // convert the extension to upper case
        unsigned extN = strlen(pp->extStr);
        char ext[ extN+1 ];
        for(unsigned i=0; i<extN; ++i)
          ext[i] = toupper(pp->extStr[i]);
        ext[extN] = 0;
      
        if( strcmp(ext,"WAV") == 0 )
        {
          flagsRef = cwSetFlag(flagsRef, kWavAfFl);
          flagsRef = cwClrFlag(flagsRef, kAiffAfFl);      
        }
        else
          if( strcmp(ext,"AIF")==0 || strcmp(ext,"AIFF")==0 )
          {
            flagsRef = cwClrFlag(flagsRef, kWavAfFl);
            flagsRef = cwSetFlag(flagsRef, kAiffAfFl);      
          }
          else
          {
            rc = cwLogError(kInvalidArgRC,"The audio file extension '%s' does not match WAV,AIFF,or AIF.", ext );
          }
      }
    errLabel:      
      mem::release(pp);

      return rc;
      
    }

    rc_t _readInt( handle_t h, unsigned totalFrmCnt, unsigned chIdx, unsigned chCnt, int* buf[], unsigned* actualFrmCntPtr, bool sumFl )
    {
      rc_t    rc = kOkRC;
      af_t* p  = _handleToPtr(h);

      //if( p->flags & kWriteFloatFl )
      //  return cwLogError(kOpFailRC,"Conversion from floating point samples to integer output is not implemented.");

      if( chIdx+chCnt > p->info.chCnt )
        return cwLogError(kInvalidArgRC,"Invalid channel index on read. %i > %i",chIdx+chCnt,chCnt);

      if( actualFrmCntPtr != NULL )
        *actualFrmCntPtr = 0;

      unsigned       bps            = p->info.bits / kBitsPerByte;       // bytes per sample
      unsigned       bpf            = bps * p->info.chCnt;    // bytes per file frame
      unsigned       bufFrmCnt      = std::min(totalFrmCnt,(unsigned)cwAudioFile_MAX_FRAME_READ_CNT);
      unsigned       bytesPerBuf    = bufFrmCnt * bpf;
      unsigned char  fbuf[ bytesPerBuf ];                     // raw bytes buffer 
      unsigned       ci;
      unsigned       frmCnt = 0;  
      unsigned       totalReadFrmCnt;
      int*           ptrBuf[ chCnt ];
  

      for(ci=0; ci<chCnt; ++ci)
        ptrBuf[ci] = buf[ci];

      for(totalReadFrmCnt=0; totalReadFrmCnt<totalFrmCnt; totalReadFrmCnt+=frmCnt )
      {
    
        // don't read past the end of the file or past the end of the buffer
        frmCnt = std::min( p->info.frameCnt - p->curFrmIdx, std::min( totalFrmCnt-totalReadFrmCnt, bufFrmCnt ));


        // read the file frmCnt sample 
        if((rc = _read(p,fbuf,frmCnt*bpf,1)) != kOkRC )
          return rc;

        if( actualFrmCntPtr != NULL )
          *actualFrmCntPtr += frmCnt;

        assert( chIdx+chCnt <= p->info.chCnt );


        for(ci=0; ci<chCnt; ++ci)
        {
          unsigned char* sp = fbuf + (ci+chIdx)*bps;
          int*           dp = ptrBuf[ci];
          int*           ep = dp + frmCnt;

          if( !sumFl )
            memset(dp,0,frmCnt*sizeof(int));

          // 8 bit AIF files use 'signed char' and WAV files use 'unsigned char' for the sample data type. 
          if( p->info.bits == 8 )
          {
            if( cwIsFlag(p->info.flags,kAiffAfFl) )
            {
              for(; dp<ep; sp+=bpf,++dp)
                *dp +=  *(char*)sp;
            }
            else
            {
              for(; dp<ep; sp+=bpf,++dp)
              {
                int v = *(unsigned char*)sp;
                *dp +=  v -= 128;
              }
            }

          }

          // handle non-8 bit files here
          if( cwIsFlag(p->info.flags,kSwapSamplesAfFl) )
          {
            switch( p->info.bits )
            {
              case 8:
                break;

              case 16:
                for(; dp<ep; sp+=bpf,++dp)
                  *dp += (short)_cmAfSwap16(*(short*)sp);
                break;

              case 24:
                if( cwIsFlag(p->info.flags,kAiffAfFl) )
                {
                  for(; dp<ep; sp+=bpf,++dp)
                    *dp += (int)(_cmAfSwap32(_24to32_aif(sp)));
                }
                else
                {
                  for(; dp<ep; sp+=bpf,++dp)
                    *dp += (int)(_cmAfSwap32(_24to32_wav(sp)));
                }
                break;

              case 32:
                for(; dp<ep; sp+=bpf,++dp)
                  *dp += (int)_cmAfSwap32(*(int*)sp  );
                break;
            }
          }
          else
          {
            switch(p->info.bits)
            {
              case 8:
                break;

              case 16:
                for(; dp<ep; sp+=bpf,++dp)
                  *dp += *(short*)sp;
                break;

              case 24:
                if( cwIsFlag(p->info.flags,kAiffAfFl) )
                {
                  for(; dp<ep; sp+=bpf,++dp)
                    *dp +=  _24to32_aif(sp);
                }
                else
                {
                  for(; dp<ep; sp+=bpf,++dp)
                    *dp +=  _24to32_wav(sp);
                }
                break;

              case 32:
                for(; dp<ep; sp+=bpf,++dp)
                  *dp += *(int*)sp;


                break;
            }

            ptrBuf[ci] = dp;
            assert( dp <= buf[ci] + totalFrmCnt );
          }
          /*
            dp = ptrBuf[ci];
            ep = dp + frmCnt;
            while(dp<ep)
            sum += (double)*dp++;
          */
        }

        p->curFrmIdx += frmCnt;
      }

      if( totalReadFrmCnt < totalFrmCnt  )
      {
        for(ci=0; ci<chCnt; ++ci)
          memset(buf[ci] + frmCnt,0,(totalFrmCnt-totalReadFrmCnt)*sizeof(int));
      }



      //if( actualFrmCntPtr != NULL )
      //  *actualFrmCntPtr = totalReadFrmCnt;

      //printf("SUM: %f %f swap:%i\n", sum, sum/(totalFrmCnt*chCnt), cwIsFlag(p->info.flags,kSwapAfFl));

      return rc;  
    }

    rc_t _readRealSamples(  handle_t h, unsigned totalFrmCnt, unsigned chIdx, unsigned chCnt, float**  fbuf, double** dbuf, unsigned* actualFrmCntPtr, bool sumFl )
    {
      rc_t    rc = kOkRC;
      af_t* p  = _handleToPtr(h);

      if( actualFrmCntPtr != NULL )
        *actualFrmCntPtr = 0;

      unsigned         totalReadCnt = 0;
      unsigned         bufFrmCnt    = std::min( totalFrmCnt, (unsigned)cwAudioFile_MAX_FRAME_READ_CNT );
      unsigned         bufSmpCnt    = bufFrmCnt * chCnt;
      float            fltMaxSmpVal = 0;  

      int              buf[ bufSmpCnt ];
      int*             ptrBuf[ chCnt ];
      float*           fPtrBuf[ chCnt ];
      double*          dPtrBuf[ chCnt ];
      unsigned         i;
      unsigned         frmCnt = 0;

      switch( p->info.bits )
      {
        case 8:   fltMaxSmpVal = 0x80;       break;
        case 16:  fltMaxSmpVal = 0x8000;     break;
        case 24:  fltMaxSmpVal = 0x800000;   break;
        case 32:  fltMaxSmpVal = 0x80000000; break;
        default:
          return cwLogError(kInvalidArgRC,"Audio file invalid sample word size:%i bits.",p->info.bits);
      }
      
      double         dblMaxSmpVal = fltMaxSmpVal;

      // initialize the audio ptr buffers
      for(i=0; i<chCnt; ++i)
      {
        ptrBuf[i] = buf + (i*bufFrmCnt);

        if( dbuf != NULL )
          dPtrBuf[i] = dbuf[i];

        if( fbuf != NULL )
          fPtrBuf[i] = fbuf[i];
      }

      // 
      for(totalReadCnt=0; totalReadCnt<totalFrmCnt && p->curFrmIdx < p->info.frameCnt; totalReadCnt+=frmCnt)
      {
        unsigned actualReadFrmCnt = 0;
        frmCnt = std::min( p->info.frameCnt - p->curFrmIdx, std::min( totalFrmCnt-totalReadCnt, bufFrmCnt ) );

        // Fill the integer audio buffer from the file.
        // Note that the input format may be float but this is ok since there size is the same and _readInt() does not processes the values.
        if((rc = _readInt( h, frmCnt, chIdx, chCnt, ptrBuf, &actualReadFrmCnt, false )) != kOkRC )
          return rc;

        if( actualFrmCntPtr != NULL )
          *actualFrmCntPtr += actualReadFrmCnt;

        // Convert the integer buffer to floating point
        for(i=0; i<chCnt; ++i)
        {

          // if the input is already in float format
          if( p->flags & kWriteFloatFl )
          {
            float* sp = (float*)ptrBuf[i];
            
            // if output is in float (not double) format
            if( fbuf != NULL )
            {
              float* dp  = fPtrBuf[i];
              float* ep = dp + frmCnt;

              if( sumFl )
              {
                for(; dp<ep; ++dp,++sp)
                  *dp +=  *sp;

              }
              else
              {
                for(; dp<ep; ++dp,++sp)
                  *dp = *sp;
              }

              assert( dp <= fbuf[i] + totalFrmCnt );

              fPtrBuf[i] = dp;
            }
            else // uf output is in double (not float) format
            {

              double* dp = dPtrBuf[i];
              double* ep = dp + frmCnt;

              if( sumFl )
              {
                for(; dp<ep; ++dp,++sp)
                  *dp += ((double)*sp);                
              }
              else
              {
                for(; dp<ep; ++dp,++sp)
                  *dp = ((double)*sp);      
              }

              assert( dp <= dbuf[i] + totalFrmCnt );
              dPtrBuf[i] = dp;
              
              
            }
            
          }
          else // if input is an integer format (not float format)
          {
            int* sp = ptrBuf[i];
            
            // if output is float (not double)
            if( fbuf != NULL )
            {

              float* dp  = fPtrBuf[i];
              float* ep = dp + frmCnt;

              if( sumFl )
              {
                for(; dp<ep; ++dp,++sp)
                  *dp += ((float)*sp) / fltMaxSmpVal;

              }
              else
              {
                for(; dp<ep; ++dp,++sp)
                  *dp = ((float)*sp) / fltMaxSmpVal;
              }

              assert( dp <= fbuf[i] + totalFrmCnt );

              fPtrBuf[i] = dp;
            }
            else // output is double (not float)
            {
              double* dp = dPtrBuf[i];
              double* ep = dp + frmCnt;

              if( sumFl )
              {
                for(; dp<ep; ++dp,++sp)
                  *dp += ((double)*sp) / dblMaxSmpVal;                
              }
              else
              {
                for(; dp<ep; ++dp,++sp)
                  *dp = ((double)*sp) / dblMaxSmpVal;      
              }

              assert( dp <= dbuf[i] + totalFrmCnt );
              dPtrBuf[i] = dp;
            }
          }
        }
      }


      return rc;
    }

    rc_t _readFloat( handle_t h, unsigned frmCnt, unsigned chIdx, unsigned chCnt, float** buf, unsigned* actualFrmCntPtr, bool sumFl )
    {
      return _readRealSamples(h,frmCnt,chIdx,chCnt,buf, NULL, actualFrmCntPtr, sumFl );
    }

    rc_t _readDouble( handle_t h, unsigned frmCnt, unsigned chIdx, unsigned chCnt, double** buf, unsigned* actualFrmCntPtr, bool sumFl )
    {
      return _readRealSamples(h,frmCnt,chIdx,chCnt,NULL, buf, actualFrmCntPtr, sumFl );
    }

    

    
    rc_t _get( handle_t& h, const char* fn, unsigned begFrmIdx, info_t* afInfoPtr )
    {
      rc_t rc;

      if((rc = open( h, fn, afInfoPtr )) == kOkRC )
        if( begFrmIdx > 0 )
          rc = seek( h, begFrmIdx );
       
      return rc;
    }

    rc_t     _getInt( const char* fn, unsigned begFrmIdx, unsigned frmCnt, unsigned chIdx, unsigned chCnt, int**    buf, unsigned* actualFrmCntPtr, info_t* afInfoPtr, bool sumFl )
    {
      rc_t     rc;
      handle_t h;

      if((rc = _get(h,fn,begFrmIdx,afInfoPtr)) == kOkRC )        
        rc = _readInt(h, frmCnt, chIdx,  chCnt, buf, actualFrmCntPtr, sumFl );

      close(h);
  
      return rc;
    }

    rc_t     _getFloat(  const char* fn, unsigned begFrmIdx, unsigned frmCnt, unsigned chIdx, unsigned chCnt, float**  buf, unsigned* actualFrmCntPtr, info_t* afInfoPtr, bool sumFl )
    {
      rc_t     rc;
      handle_t h;

      if((rc = _get(h,fn,begFrmIdx,afInfoPtr)) == kOkRC )
        rc = _readFloat(h, frmCnt, chIdx,  chCnt, buf, actualFrmCntPtr, sumFl );

      close(h);
  
      return rc;
    }

    rc_t     _getDouble( const char* fn, unsigned begFrmIdx, unsigned frmCnt, unsigned chIdx, unsigned chCnt, double** buf, unsigned* actualFrmCntPtr, info_t* afInfoPtr, bool sumFl )
    {
      rc_t     rc;
      handle_t h;

      if((rc = _get(h,fn,begFrmIdx,afInfoPtr)) == kOkRC )
        rc = _readDouble(h, frmCnt, chIdx,  chCnt, buf, actualFrmCntPtr, sumFl );

      close(h);
  
      return rc;
    }

    rc_t _write_samples_to_file( af_t* p, unsigned bytesPerSmp, unsigned bufSmpCnt, const void* buf )
    {
      rc_t rc = kOkRC;
      
      if( fwrite( (void*)buf, bufSmpCnt*bytesPerSmp, 1, p->fp ) != 1)
        rc = cwLogError(kWriteFailRC,"Audio file write failed on '%s'.",cwStringNullGuard(p->fn));

      return rc;
    }


    //
    //   Write 8 bit samples
    //

    // sample writer: int->uint8_t
    rc_t _write_samples( af_t* p, unsigned bufSmpCnt, const int* sbuf, int8_t* dbuf )
    {
      for(unsigned i=0; i<bufSmpCnt; ++i)
        dbuf[i] =  (((long)sbuf[i]*255) / ((long)INT_MAX - INT_MIN)) + 127;
       return _write_samples_to_file(p,sizeof(dbuf[0]),bufSmpCnt,dbuf);
    }

    // sample writer:  float->uint8_t
    rc_t _write_samples( af_t* p, unsigned bufSmpCnt, const float* sbuf, int8_t* dbuf )
    {
      for(unsigned i=0; i<bufSmpCnt; ++i)
        dbuf[i] =  (uint8_t)(sbuf[i] * 128) + 127; 
       return _write_samples_to_file(p,sizeof(dbuf[0]),bufSmpCnt,dbuf);      
    }

    // sample writer:  double->uint8_t    
    rc_t _write_samples( af_t* p, unsigned bufSmpCnt, const double* sbuf, int8_t* dbuf )
    {
      for(unsigned i=0; i<bufSmpCnt; ++i)
        dbuf[i] =  (uint8_t)(sbuf[i] * 128) + 127; 
       return _write_samples_to_file(p,sizeof(dbuf[0]),bufSmpCnt,dbuf);      
    }


    //
    //   Write 16 bit samples
    //
    
    // sample writer: int->short
    rc_t _write_samples( af_t* p, unsigned bufSmpCnt, const int* sbuf, short* dbuf )
    {
      for(unsigned i=0; i<bufSmpCnt; ++i)
        dbuf[i] =  ((long)sbuf[i]*2*SHRT_MAX) / ((long)INT_MAX - INT_MIN);
       return _write_samples_to_file(p,sizeof(dbuf[0]),bufSmpCnt,dbuf);
    }

    // sample writer: float->short
    rc_t _write_samples( af_t* p, unsigned bufSmpCnt, const float* sbuf, short* dbuf )
    {
      for(unsigned i=0; i<bufSmpCnt; ++i)
        dbuf[i] =  (short)(sbuf[i] * SHRT_MAX);
       return _write_samples_to_file(p,sizeof(dbuf[0]),bufSmpCnt,dbuf);      
    }

    // sample writer: double->short    
    rc_t _write_samples( af_t* p, unsigned bufSmpCnt, const double* sbuf, short* dbuf )
    {
      for(unsigned i=0; i<bufSmpCnt; ++i)
        dbuf[i] =  (short)(sbuf[i] * SHRT_MAX);
       return _write_samples_to_file(p,sizeof(dbuf[0]),bufSmpCnt,dbuf);      
    }


    //
    //   Write 24 bit samples
    //

    inline void int_to_24( int v, uint8_t* d )
    {
      uint8_t* s = (uint8_t*)(&v);
      
      d[0] = s[0];
      d[1] = s[1];
      d[2] = s[2];
    }

    // sample writer: int->int24
    rc_t _write_samples( af_t* p, unsigned bufSmpCnt, const int* sbuf, uint32_t* dbuf )
    {
      uint8_t* dbp = (uint8_t*)dbuf;
      
      for(unsigned i=0; i<bufSmpCnt; dbp+=3, ++i)
        int_to_24(sbuf[i],dbp);
      
      return _write_samples_to_file(p,3,bufSmpCnt,dbuf);
    }

    // sample writer: float->int24
    rc_t _write_samples( af_t* p, unsigned bufSmpCnt, const float* sbuf, uint32_t* dbuf )
    {
      uint8_t* dbp = (uint8_t*)dbuf;
      
      for(unsigned i=0; i<bufSmpCnt; dbp+=3, ++i)
      {
        int ismp = (int)(sbuf[i]*(int)kMax24Bits);
        int_to_24(ismp,dbp);
      }
      
      return _write_samples_to_file(p,sizeof(dbuf[0]),bufSmpCnt,dbuf);      
    }

    // sample writer: double->int24    
    rc_t _write_samples( af_t* p, unsigned bufSmpCnt, const double* sbuf, uint32_t* dbuf )
    {
      uint8_t* dbp = (uint8_t*)dbuf;
      
      for(unsigned i=0; i<bufSmpCnt; dbp+=3, ++i)
      {
        int ismp = (int)(sbuf[i]*(int)kMax24Bits);
        int_to_24(ismp,dbp);
      }
      return _write_samples_to_file(p,sizeof(dbuf[0]),bufSmpCnt,dbuf);      
    }
    
    
    //
    //   Write 32 bit samples
    //
    
    // sample writer: int->int
    rc_t _write_samples( af_t* p, unsigned bufSmpCnt, const int* sbuf, int* dbuf )
    {
      return _write_samples_to_file(p,sizeof(dbuf[0]),bufSmpCnt,dbuf);
    }

    // float->int
    rc_t _write_samples( af_t* p, unsigned bufSmpCnt, const float* sbuf, int* dbuf )
    {
      for(unsigned i=0; i<bufSmpCnt; ++i)
        dbuf[i] =  (int)(sbuf[i] * INT_MAX);
      return _write_samples_to_file(p,sizeof(dbuf[0]),bufSmpCnt,dbuf);      
    }

    // double->int    
    rc_t _write_samples( af_t* p, unsigned bufSmpCnt, const double* sbuf, int* dbuf )
    {
      for(unsigned i=0; i<bufSmpCnt; ++i)
        dbuf[i] =  (int)(sbuf[i] * INT_MAX);
      return _write_samples_to_file(p,sizeof(dbuf[0]),bufSmpCnt,dbuf);      
    }

    //
    //   Write float samples
    //
    
    // int->float
    rc_t _write_samples( af_t* p, unsigned bufSmpCnt, const int* sbuf, float* dbuf )
    {
      for(unsigned i=0; i<bufSmpCnt; ++i)
        dbuf[i] = sbuf[i]/INT_MAX;
      return _write_samples_to_file(p,sizeof(dbuf[0]),bufSmpCnt,dbuf);
    }

    // float->float
    rc_t _write_samples( af_t* p, unsigned bufSmpCnt, const float* sbuf, float* dbuf )
    {
      //memcpy(dbuf,sbuf,bufSmpCnt*sizeof(float));
      //return _write_samples_to_file(p,sizeof(dbuf[0]),bufSmpCnt,dbuf);
      return _write_samples_to_file(p,sizeof(sbuf[0]),bufSmpCnt,sbuf);
    }

    // double->float    
    rc_t _write_samples( af_t* p, unsigned bufSmpCnt, const double* sbuf, float* dbuf )
    {
      for(unsigned i=0; i<bufSmpCnt; ++i)
        dbuf[i] =  (float)(sbuf[i]);
      return _write_samples_to_file(p,sizeof(dbuf[0]),bufSmpCnt,dbuf);      
    }

    
    
    template< typename S, typename D >
    rc_t _write_samples( af_t* p, unsigned bufSmpCnt, const S* sbuf, D* dbuf)
    {
      return cwLogError(kInvalidDataTypeRC,"The source and desintation sample types are not supported by the audio file writer.");
    }

    template< typename S, typename D >
    rc_t _write_audio( af_t* p, unsigned srcBufFrmCnt, unsigned chCnt, const S* const * srcPtrPtr )
    {
      rc_t rc = kOkRC;
      
      // Determine the size of the temporary buffer used for interleaving, type conversion and file writing
      unsigned bufFrmCnt = std::min(srcBufFrmCnt,4096u);
      unsigned bufSmpCnt = bufFrmCnt*chCnt;

      // allocate the temp. buffers
      S sbuf[ bufSmpCnt ];
      D dbuf[ bufSmpCnt ];

      // for each frame in the source
      for(unsigned sfi=0; sfi<srcBufFrmCnt; )
      {
        // copy as many samples as are available into the temp. buffer
        unsigned copyFrmN = std::min( bufFrmCnt, srcBufFrmCnt - sfi );

        // interleave the source channel signal arrays into the temp buffer
        for(unsigned fi=0,si=0; fi<copyFrmN; ++fi)
          for(unsigned sci=0; sci<chCnt; ++sci)
            sbuf[si++] = srcPtrPtr[sci][sfi+fi];
          
        // convert the sample data types and write the result to the output file
        if((rc = _write_samples(p,copyFrmN*chCnt,sbuf,dbuf)) != kOkRC )
          break;

        p->info.frameCnt += copyFrmN;
        sfi              += copyFrmN;
        
      }
      
      return rc;
    }
    
    /*
    rc_t    _writeRealSamples( handle_t h, unsigned frmCnt, unsigned chCnt, const void*  srcPtrPtr, unsigned realSmpByteCnt )
    {
      rc_t  rc = kOkRC;
      af_t* p  = _handleToPtr(h);

      unsigned         bufFrmCnt = 1024;
      unsigned         wrFrmCnt  = 0;
      unsigned         i         = 0;
      int              maxSmpVal = 0;

      int              buf[   chCnt * bufFrmCnt ];
      int*             srcCh[ chCnt ];
  
      for(i=0; i<chCnt; ++i)
        srcCh[i] = buf + (i*bufFrmCnt);

      switch( p->info.bits )
      {
        case 8:   maxSmpVal = 0x7f;       break;
        case 16:  maxSmpVal = 0x7fff;     break;
        case 24:  maxSmpVal = 0x7fffff;   break;
        case 32:  maxSmpVal = 0x7fffffb0; break; // Note: the full range is not used for 32 bit numbers
        default:                                 // because it was found to cause difficult to detect overflows
          { assert(0); }                         // when the signal approached full scale. 
      }

      // duplicate the audio buffer ptr array - this will allow the buffer ptr's to be changed
      // during the float to int conversion without changing the ptrs passed in from the client
      const void* ptrArray[ chCnt ];
      memcpy(ptrArray,srcPtrPtr,sizeof(ptrArray));

      const float**  sfpp = (const float**)ptrArray;
      const double** sdpp = (const double**)ptrArray;

      while( wrFrmCnt < frmCnt )
      {
        unsigned n = std::min( frmCnt - wrFrmCnt, bufFrmCnt );

        for(i=0; i<chCnt; ++i)
        {
          int*       obp = srcCh[i];

          switch( realSmpByteCnt )
          {
            case 4:
              {
                const float* sbp = sfpp[i];
                const float* sep = sbp + n;
                for(;sbp<sep; ++sbp)
                {
                  *obp++ = (int)fmaxf(-maxSmpVal,fminf(maxSmpVal, *sbp * maxSmpVal));
                }

                sfpp[i] = sbp;
              }
              break;
 
            case 8:
              {
                const double* sbp = sdpp[i];
                const double* sep = sbp + n;
                for(; sbp<sep; ++sbp)
                {
                  *obp++ = (int)fmax(-maxSmpVal,fmin(maxSmpVal,*sbp * maxSmpVal));
                }
                sdpp[i] = sbp;
              }
              break;

            default:
              { assert(0); }
          }  
        }

        if((rc = writeInt( h, n, chCnt, srcCh )) != kOkRC )
          break;

        wrFrmCnt += n;
      }

      return rc;
    }
    */
    
    void _test( const char* audioFn )
    {
      info_t afInfo;
      rc_t   rc;

      // open an audio file
      handle_t     afH;
      if((rc = open( afH, audioFn, &afInfo )) == kOkRC )
      {        
        report( afH, log::globalHandle(), 0, 0);
        close(afH);
      }
      
    }

    rc_t _testReport( const object_t* cfg )
    {
      rc_t        rc     = kOkRC;
      const char* fn     = nullptr;
      unsigned    begIdx = 0;
      unsigned    frmN   = 0;
      
      if((rc = cfg->getv("fn",fn,"begIdx",begIdx,"frmCnt",frmN)) != kOkRC )
        return cwLogError(kSyntaxErrorRC,"Invalid parameter to audio file report test.");
      
      char* afn = filesys::expandPath(fn);
      rc = reportFn(afn,log::globalHandle(),begIdx,frmN);
      mem::release(afn);
      
      return rc;
    }
  }
}


cw::rc_t cw::audiofile::open( handle_t& h, const char* fn, info_t* info )
{
  rc_t rc;
  if((rc = close(h)) != kOkRC )
    return rc;

  af_t* p = mem::allocZ<af_t>(1);

  // read the file header
  if((rc = _open(p, fn, "rb" )) != kOkRC )
    goto errLabel;

  // seek to the first sample offset
  if((rc = _seek(p,p->smpByteOffs,SEEK_SET)) != kOkRC )
    goto errLabel;

  p->fn = mem::duplStr( fn );

  if( info != NULL)
    memcpy(info,&p->info,sizeof(info_t));
  
  h.set(p);
 errLabel:
  return rc;
}

    
cw::rc_t cw::audiofile::create( handle_t& h, const char* fname, double srate, unsigned bits, unsigned chCnt )
{
  rc_t rc = kOkRC;
  af_t* p = nullptr;
  char* fn = nullptr;
  
  if((rc = close(h)) != kOkRC )
    return rc;

  if( fname == NULL || strlen(fname)==0 )
    return cwLogError(kInvalidArgRC,"Audio file create failed. The file name is blank.");

  if((fn = filesys::expandPath(fname)) == nullptr )
  {
    rc = cwLogError(kOpFailRC,"File path expansion failed on '%s'.",cwStringNullGuard(fname));
    goto errLabel;
  }
  
  p = mem::allocZ<af_t>(1);
  
  if((rc =  _filenameToAudioFileType(fn, p->info.flags )) != kOkRC )
    goto errLabel;
  
  // open the file for writing
  if((p->fp = fopen(fn,"wb")) == NULL )
  {
    rc = cwLogError(kOpenFailRC,"Audio file create failed on '%s'.",cwStringNullGuard(fn));
    goto errLabel;
  }
  else
  {
    
    p->fn            = mem::duplStr( fn );
    p->info.srate    = srate;
    p->info.bits     = bits==0 ? sizeof(float)*kBitsPerByte : bits;
    p->info.chCnt    = chCnt;
    p->info.frameCnt = 0;
    p->flags         = kWriteAudioGutsFl + (bits==0 ? kWriteFloatFl : 0);
    
    // set the swap flags
    bool swapFl = cwIsFlag(p->info.flags,kWavAfFl) ?  _cmWavSwapFl :  _cmAifSwapFl;
    
    p->info.flags = cwEnaFlag( p->info.flags, kSwapAfFl,        swapFl);
    p->info.flags = cwEnaFlag( p->info.flags, kSwapSamplesAfFl, swapFl);
    
    if((rc = _writeHdr(p)) != kOkRC )
      goto errLabel;
    
  
    h.set(p);
  }
 errLabel:

  mem::release(fn);
  
  if( rc != kOkRC )
    _destroy(p);
  
  return rc;
}
    
cw::rc_t cw::audiofile::close( handle_t& h )
{
  rc_t rc = kOkRC;
  if( !h.isValid() )
    return rc;

  af_t* p = _handleToPtr(h);

  if((rc = _destroy(p)) != kOkRC )
    return rc;
  
  h.clear();
  return rc;
}



bool     cw::audiofile::isOpen( handle_t h )
{
  if( !h.isValid() )
    return false;
 
  return _handleToPtr(h)->fp != NULL;
}


bool   cw::audiofile::isEOF(      handle_t h )
{
  af_t* p  = _handleToPtr(h);
  return  (p->curFrmIdx >= p->info.frameCnt) || (p->fp==NULL) ||  feof(p->fp) ? true : false;
}

unsigned   cw::audiofile::tell(       handle_t h )
{
  af_t* p  = _handleToPtr(h);
  return p->curFrmIdx;
}

cw::rc_t     cw::audiofile::seek(       handle_t h, unsigned frmIdx )
{
  rc_t  rc = kOkRC;
  af_t* p  = _handleToPtr(h);
  
  if((rc = _seek(p,p->smpByteOffs + (frmIdx * p->info.chCnt * (p->info.bits/kBitsPerByte)), SEEK_SET)) != kOkRC )
    return rc;

  p->curFrmIdx = frmIdx;

  return rc;

}


cw::rc_t     cw::audiofile::readInt(    handle_t h, unsigned frmCnt, unsigned chIdx, unsigned chCnt, int**    buf, unsigned* actualFrmCntPtr )
{ return _readInt( h, frmCnt, chIdx, chCnt, buf, actualFrmCntPtr, false ); }

cw::rc_t     cw::audiofile::readFloat(  handle_t h, unsigned frmCnt, unsigned chIdx, unsigned chCnt, float**  buf, unsigned* actualFrmCntPtr )
{ return _readFloat( h, frmCnt, chIdx, chCnt, buf, actualFrmCntPtr, false ); }

cw::rc_t     cw::audiofile::readDouble( handle_t h, unsigned frmCnt, unsigned chIdx, unsigned chCnt, double** buf, unsigned* actualFrmCntPtr )
{ return _readDouble( h, frmCnt, chIdx, chCnt, buf, actualFrmCntPtr, false ); }

cw::rc_t     cw::audiofile::readSumInt(    handle_t h, unsigned frmCnt, unsigned chIdx, unsigned chCnt, int**    buf, unsigned* actualFrmCntPtr )
{ return _readInt( h, frmCnt, chIdx, chCnt, buf, actualFrmCntPtr, true ); }

cw::rc_t     cw::audiofile::readSumFloat(  handle_t h, unsigned frmCnt, unsigned chIdx, unsigned chCnt, float**  buf, unsigned* actualFrmCntPtr )
{ return _readFloat( h, frmCnt, chIdx, chCnt, buf, actualFrmCntPtr, true ); }

cw::rc_t     cw::audiofile::readSumDouble( handle_t h, unsigned frmCnt, unsigned chIdx, unsigned chCnt, double** buf, unsigned* actualFrmCntPtr )
{ return _readDouble( h, frmCnt, chIdx, chCnt, buf, actualFrmCntPtr, true ); }


cw::rc_t     cw::audiofile::getInt(    const char* fn, unsigned begFrmIdx, unsigned frmCnt, unsigned chIdx, unsigned chCnt, int**    buf, unsigned* actualFrmCntPtr, info_t* afInfoPtr )
{ return _getInt( fn, begFrmIdx, frmCnt, chIdx, chCnt, buf, actualFrmCntPtr, afInfoPtr, false ); }

cw::rc_t     cw::audiofile::getFloat(  const char* fn, unsigned begFrmIdx, unsigned frmCnt, unsigned chIdx, unsigned chCnt, float**  buf, unsigned* actualFrmCntPtr, info_t* afInfoPtr )
{ return _getFloat( fn, begFrmIdx, frmCnt, chIdx, chCnt, buf, actualFrmCntPtr, afInfoPtr, false ); }

cw::rc_t     cw::audiofile::getDouble( const char* fn, unsigned begFrmIdx, unsigned frmCnt, unsigned chIdx, unsigned chCnt, double** buf, unsigned* actualFrmCntPtr, info_t* afInfoPtr )
{ return _getDouble( fn, begFrmIdx, frmCnt, chIdx, chCnt, buf, actualFrmCntPtr, afInfoPtr, false); }

cw::rc_t     cw::audiofile::getSumInt(    const char* fn, unsigned begFrmIdx, unsigned frmCnt, unsigned chIdx, unsigned chCnt, int**    buf, unsigned* actualFrmCntPtr, info_t* afInfoPtr )
{ return _getInt( fn, begFrmIdx, frmCnt, chIdx, chCnt, buf, actualFrmCntPtr, afInfoPtr, true ); }

cw::rc_t     cw::audiofile::getSumFloat(  const char* fn, unsigned begFrmIdx, unsigned frmCnt, unsigned chIdx, unsigned chCnt, float**  buf, unsigned* actualFrmCntPtr, info_t* afInfoPtr )
{ return _getFloat( fn, begFrmIdx, frmCnt, chIdx, chCnt, buf, actualFrmCntPtr, afInfoPtr, true ); }

cw::rc_t     cw::audiofile::getSumDouble( const char* fn, unsigned begFrmIdx, unsigned frmCnt, unsigned chIdx, unsigned chCnt, double** buf, unsigned* actualFrmCntPtr, info_t* afInfoPtr )
{ return _getDouble( fn, begFrmIdx, frmCnt, chIdx, chCnt, buf, actualFrmCntPtr, afInfoPtr, true ); }

cw::rc_t     cw::audiofile::allocFloatBuf( const char* fn, float**& chBufRef, unsigned& chCntRef, unsigned& frmCntRef, info_t& afInfo, unsigned begFrmIdx, unsigned frmCnt, unsigned chIdx, unsigned chCnt )
{
  rc_t rc;

  unsigned actualFrmCnt = 0;
  
  frmCntRef = 0;
  chCntRef  = 0;
  
  if((rc = getInfo(fn, &afInfo )) != kOkRC )
    goto errLabel;
    
  if( chCnt == 0 )
    chCnt = afInfo.chCnt;
  else
  {
    if( chIdx + chCnt > afInfo.chCnt )
    {
      cwLogError(kInvalidArgRC,"Requested channel indexes %i to %i exceeds available channel count %i.",chIdx,chIdx+chCnt-1,afInfo,chCnt);
      goto errLabel;
    }        
  }

  if( frmCnt == 0 )
    frmCnt = afInfo.frameCnt;
 
  if( begFrmIdx + frmCnt > afInfo.frameCnt )
  {
    cwLogError(kInvalidArgRC,"Requested frames %i to %i exceeds available frame count %i.",begFrmIdx,begFrmIdx+frmCnt,afInfo.frameCnt);
    goto errLabel;
  }
 
  chBufRef = mem::allocZ< float* >(chCnt);
  for(unsigned i=0; i<chCnt; ++i)
    chBufRef[i] = mem::alloc<float>(frmCnt);
        
  
  if((rc = getFloat(fn, begFrmIdx, frmCnt, chIdx, chCnt, chBufRef, &actualFrmCnt, nullptr)) != kOkRC )
    goto errLabel;

  frmCntRef = actualFrmCnt;
  chCntRef  = chCnt;
  
 errLabel:
  if( rc != kOkRC )
    cwLogError(rc,"Audio file allocFloat() failed.");
  return rc;
}

cw::rc_t    cw::audiofile::freeFloatBuf( float** floatBuf, unsigned chCnt )
{
  for(unsigned i=0; i<chCnt; ++i)
    mem::release(floatBuf[i]);
  mem::release(floatBuf);
  return kOkRC;
}


cw::rc_t    cw::audiofile::writeInt(    handle_t h, unsigned frmCnt, unsigned chCnt, const int* const* srcPtrPtr )
{
  rc_t     rc          = kOkRC;
  af_t*    p           = _handleToPtr(h);
  unsigned bytesPerSmp = p->info.bits / kBitsPerByte;
  unsigned bufFrmCnt   = 1024;
  unsigned bufByteCnt  = bufFrmCnt * bytesPerSmp;
  unsigned ci;
  unsigned wrFrmCnt    = 0;
  char     buf[ bufByteCnt * chCnt ];
  
  while( wrFrmCnt < frmCnt )
  {
    unsigned n = std::min( frmCnt-wrFrmCnt, bufFrmCnt );

    // interleave each channel into buf[]
    for(ci=0; ci<chCnt; ++ci)
    {
      // get the begin and end source pointers
      const int* sbp = srcPtrPtr[ci] + wrFrmCnt;
      const int* sep = sbp + n;

      // 8 bit samples can't be byte swapped
      if( p->info.bits == 8 )
      {
        char*  dbp = buf + ci;
        for(; sbp < sep; dbp+=chCnt )
          *dbp = (char)*sbp++;             
      }
      else
      {
        // if the samples do need to be byte swapped
        if( cwIsFlag(p->info.flags,kSwapSamplesAfFl) )
        {
          switch( p->info.bits )
          {
            case 16:
              {
                short*  dbp = (short*)buf;
                for(dbp+=ci; sbp < sep; dbp+=chCnt, ++sbp )
                  *dbp = _cmAfSwap16((short)*sbp);
              }
              break;

            case 24:
              {
                unsigned char* dbp = (unsigned char*)buf;
                for( dbp+=(ci*3); sbp < sep; dbp+=(3*chCnt), ++sbp)
                {
                  unsigned char* s = (unsigned char*)sbp;
                  dbp[0] = s[2];
                  dbp[1] = s[1];
                  dbp[2] = s[0];
                }
              }
              break;
          

            case 32:
              {
                int*  dbp = (int*)buf;
                for(dbp+=ci; sbp < sep; dbp+=chCnt, ++sbp )
                  *dbp = _cmAfSwap32(*sbp);
              }
              break;

            default:
              { assert(0);}
          } 

        }
        else // interleave without byte swapping
        {
          switch( p->info.bits )
          {
            case 16:
              {
                short*  dbp = (short*)buf;
                for(dbp+=ci; sbp < sep; dbp+=chCnt, ++sbp )
                  *dbp = (short)*sbp;
              }
              break;
              
            case 24:
              {
                unsigned char* dbp = (unsigned char*)buf;
                for( dbp+=(ci*3); sbp < sep; dbp+=(3*chCnt), ++sbp)
                {
                  unsigned char* s = (unsigned char*)sbp;
                  dbp[0] = s[0];
                  dbp[1] = s[1];
                  dbp[2] = s[2];
                }
              }
              break;
          

            case 32:
              {
                int*  dbp = (int*)buf;
                for(dbp+=ci; sbp < sep; dbp+=chCnt, ++sbp )
                  *dbp = *sbp;
              }
              break;

            default:
              { assert(0);}
          } // switch
        } // don't swap
      } // 8 bits
    } // ch

    // advance the source pointer index
    wrFrmCnt+=n;

    if( fwrite( buf, n*bytesPerSmp*chCnt, 1, p->fp ) != 1)
    {
      rc = cwLogError(kWriteFailRC,"Audio file write failed on '%s'.",cwStringNullGuard(p->fn));
      break;
    }
    
  } // while

  p->info.frameCnt += wrFrmCnt;

  return rc;
}


cw::rc_t    cw::audiofile::writeFloat(  handle_t h, unsigned frmCnt, unsigned chCnt, const float* const* srcPtrPtr )
{
  rc_t  rc = kOkRC;
  af_t* p  = _handleToPtr(h);
  
  if( p->flags & kWriteFloatFl )
    rc = _write_audio<float,float>( p, frmCnt, chCnt, srcPtrPtr );
  else
  {
    switch(p->info.bits)
    {
      case 8:
        rc = _write_audio<float,uint8_t>( p, frmCnt, chCnt, srcPtrPtr );
        break;
          
      case 16:
        rc = _write_audio<float,short>( p, frmCnt, chCnt, srcPtrPtr );            
        break;
          
      case 24:
        assert(0);
        break;
        
      case 32:
        rc = _write_audio<float,int>( p, frmCnt, chCnt, srcPtrPtr );
        break;

      default:
        cwLogError(kInvalidArgRC,"Invalid bit depth:%i",p->info.bits);
    }
  }

  return rc;
  
  //return _writeRealSamples(h,frmCnt,chCnt,bufPtrPtr,sizeof(float));
}

cw::rc_t    cw::audiofile::writeDouble( handle_t h, unsigned frmCnt, unsigned chCnt, const double* const* srcPtrPtr )
{
  rc_t  rc = kOkRC;
  af_t* p  = _handleToPtr(h);
  
  if( p->flags & kWriteFloatFl )
    rc = _write_audio<double,float>( p, frmCnt, chCnt, srcPtrPtr );
  else
  {
    switch(p->info.bits)
    {
      case 8:
        rc = _write_audio<double,uint8_t>( p, frmCnt, chCnt, srcPtrPtr );
        break;
          
      case 16:
        rc = _write_audio<double,short>( p, frmCnt, chCnt, srcPtrPtr );            
        break;
          
      case 24:
        assert(0);
        break;
        
      case 32:
        rc = _write_audio<double,int>( p, frmCnt, chCnt, srcPtrPtr );
        break;
        
      default:
        cwLogError(kInvalidArgRC,"Invalid bit depth:%i",p->info.bits);
        
    }
  }

  return rc;
  
  //return _writeRealSamples(h,frmCnt,chCnt,bufPtrPtr,sizeof(double));
}

cw::rc_t    cw::audiofile::writeFloatInterleaved( handle_t h, unsigned frmCnt, unsigned chCnt, const float* bufPtr )
{
  rc_t rc = kOkRC;
  af_t* p  = _handleToPtr(h);
  
  if((rc = _write_samples_to_file(p,sizeof(float),frmCnt*chCnt,bufPtr)) == kOkRC )
  {
    p->info.frameCnt += frmCnt;
  }

  return rc;
}


cw::rc_t    cw::audiofile::minMaxMean( handle_t h, unsigned chIdx, float* minPtr, float* maxPtr, float* meanPtr )
{
  assert( minPtr != NULL && maxPtr != NULL && meanPtr != NULL );

  *minPtr = -FLT_MAX;
  *maxPtr = FLT_MAX;
  *meanPtr = 0;

  rc_t     rc        = kOkRC;
  af_t*    p         = _handleToPtr(h);
  unsigned orgFrmIdx = p->curFrmIdx;

  if((rc = seek(h,0)) != kOkRC )
    return rc;

  *minPtr = FLT_MAX;
  *maxPtr = -FLT_MAX;

  unsigned   bufN       = 1024;
  float buf[ bufN ];
  unsigned   frmCnt     = 0;
  unsigned   actualFrmCnt;
  float* bufPtr[1] = { &buf[0] };

  for(; frmCnt<p->info.frameCnt; frmCnt+=actualFrmCnt) 
  {
    actualFrmCnt = 0;
    unsigned n = std::min( p->info.frameCnt-frmCnt, bufN );
 
    if((rc = readFloat(h, n, chIdx, 1, bufPtr, &actualFrmCnt)) != kOkRC )
      return rc;

    const float* sbp = buf;
    const float* sep = buf + actualFrmCnt;

    for(; sbp < sep; ++sbp )
    {
      *meanPtr += *sbp;
      if( *minPtr > *sbp )
        *minPtr = *sbp;
      if( *maxPtr < *sbp )
        *maxPtr = *sbp;
    }
    
  }

  if( frmCnt > 0 )
    *meanPtr /= frmCnt;
  else
    *minPtr = *maxPtr = 0;
 
  return seek( h, orgFrmIdx );

}

cw::rc_t    cw::audiofile::writeFileInt(    const char* fn, double srate, unsigned bits, unsigned frmCnt, unsigned chCnt, const int* const*  bufPtrPtr )
{
  rc_t     rc;
  handle_t h;

  if(( rc = create(h,fn,srate,bits,chCnt)) != kOkRC )
  {
    rc = writeInt( h, frmCnt, chCnt, bufPtrPtr );
    
    close(h);
  }

  return rc;  
}

cw::rc_t    cw::audiofile::writeFileFloat(  const char* fn, double srate, unsigned bits, unsigned frmCnt, unsigned chCnt, const float* const*  bufPtrPtr )
{
  rc_t     rc;
  handle_t h;
  
  if(( rc = create(h,fn,srate,bits,chCnt)) == kOkRC )
  {
    rc = writeFloat( h, frmCnt, chCnt, bufPtrPtr );
    
    close(h);
  }

  return rc;  
}

cw::rc_t    cw::audiofile::writeFileDouble( const char* fn, double srate, unsigned bits, unsigned frmCnt, unsigned chCnt, const double* const* bufPtrPtr )
{
  rc_t     rc;
  handle_t h;
  
  if(( rc = create(h,fn,srate,bits,chCnt)) == kOkRC )
  {
    rc = writeDouble( h, frmCnt, chCnt, bufPtrPtr );
    
    close(h);
  }

  return rc;  
}


cw::rc_t cw::audiofile::minMaxMeanFn( const char* fn, unsigned chIdx, float* minPtr, float* maxPtr, float* meanPtr )
{
  rc_t rc = kOkRC;
  handle_t h;

  if((rc = open(h,fn,nullptr)) == kOkRC )
  {
    rc = minMaxMean( h, chIdx, minPtr, maxPtr, meanPtr ); 
    close(h);
  }

  return rc;
}



const char* cw::audiofile::name( handle_t h )
{
  af_t* p = _handleToPtr(h);
  return p->fn;
}

unsigned    cw::audiofile::channelCount( handle_t h )
{
  af_t* p = _handleToPtr(h);
  return p->info.chCnt;
}

double      cw::audiofile::sampleRate( handle_t h )
{
  af_t* p = _handleToPtr(h);
  return p->info.srate;
}


cw::rc_t cw::audiofile::getInfo(   const char* fn, info_t* infoPtr  )
{
  rc_t rc = kOkRC;
  handle_t h;

  if((rc = open(h,fn,infoPtr)) == kOkRC )
    close(h);

  return rc;
}


void   cw::audiofile::printInfo( const info_t* infoPtr, log::handle_t logH )
{
  const char*  typeStr = "AIFF";
  const char*  swapStr = "";
  const char*  aifcStr = "";
  unsigned i;
  
  if( cwIsFlag(infoPtr->flags,kWavAfFl) )
    typeStr = "WAV";

  if( cwIsFlag(infoPtr->flags,kSwapAfFl) )
    swapStr = "Swap:On";

  if( cwIsFlag(infoPtr->flags,kAifcAfFl))
    aifcStr = "AIFC";

  cwLogPrintH(logH,"bits:%i chs:%i srate:%f frames:%i type:%s %s %s\n", infoPtr->bits, infoPtr->chCnt, infoPtr->srate, infoPtr->frameCnt, typeStr, swapStr, aifcStr );

  for(i=0; i<infoPtr->markerCnt; ++i)
    cwLogPrintH(logH,"%i %i %s\n", infoPtr->markerArray[i].id, infoPtr->markerArray[i].frameIdx, infoPtr->markerArray[i].label);

  if( strlen(infoPtr->bextRecd.desc) )
    cwLogPrintH(logH,"Bext Desc:%s\n",infoPtr->bextRecd.desc );

  if( strlen(infoPtr->bextRecd.origin) )
    cwLogPrintH(logH,"Bext Origin:%s\n",infoPtr->bextRecd.origin );

  if( strlen(infoPtr->bextRecd.originRef) )
    cwLogPrintH(logH,"Bext Origin Ref:%s\n",infoPtr->bextRecd.originRef );

  if( strlen(infoPtr->bextRecd.originDate) )
    cwLogPrintH(logH,"Bext Origin Date:%s\n",infoPtr->bextRecd.originDate );

  if( strlen(infoPtr->bextRecd.originTime ) )
    cwLogPrintH(logH,"Bext Origin Time:%s\n",infoPtr->bextRecd.originTime );

  cwLogPrintH(logH,"Bext time high:%i low:%i  0x%x%x\n",infoPtr->bextRecd.timeRefHigh,infoPtr->bextRecd.timeRefLow, infoPtr->bextRecd.timeRefHigh,infoPtr->bextRecd.timeRefLow);

}

cw::rc_t cw::audiofile::reportInfo( const char* audioFn )
{
  rc_t   rc;
  info_t info;
  
  if((rc = getInfo(audioFn,&info)) != kOkRC )
    return rc;

  printInfo(&info,log::globalHandle());
  return rc;
  
}



cw::rc_t     cw::audiofile::report( handle_t h, log::handle_t logH, unsigned frmIdx, unsigned frmCnt )
{
  rc_t  rc = kOkRC;
  af_t* p  = _handleToPtr(h);
  
  
  cwLogPrintH(logH,"function cm_audio_file_test()\n");
  cwLogPrintH(logH,"#{\n");
  printInfo(&p->info,logH);
  cwLogPrintH(logH,"#}\n");

  if( frmCnt == kInvalidCnt )
    frmCnt = p->info.frameCnt;
  

  float           buf[ p->info.chCnt * frmCnt ];
  float*          bufPtr[p->info.chCnt];
  unsigned      i,j,cmtFrmCnt=0;

  for(i=0; i<p->info.chCnt; ++i)
    bufPtr[i] = buf + (i*frmCnt);

  if((rc = seek(h,frmIdx)) != kOkRC )
    return rc;
  
  if((rc= readFloat(h,frmCnt,0,p->info.chCnt,bufPtr,&cmtFrmCnt )) != kOkRC)
    return rc;

  cwLogPrintH(logH,"m = [\n");
  for(i=0; i<frmCnt; i++)
  {
    for(j=0; j<p->info.chCnt; ++j)
      cwLogPrintH(logH,"%f ", bufPtr[j][i] );
    cwLogPrintH(logH,"\n");
  }
    cwLogPrintH(logH,"];\nplot(m)\nendfunction\n");

  return rc;
  
}

cw::rc_t     cw::audiofile::reportFn( const char* fn, log::handle_t logH, unsigned frmIdx, unsigned frmCnt )
{
  rc_t     rc;
  handle_t h;

  if((rc = open(h,fn,nullptr)) == kOkRC )
  {
    rc = report(h,logH,frmIdx,frmCnt);
    
    close(h);
  }

  return rc;
}


cw::rc_t     cw::audiofile::setSrate( const char* fn, unsigned srate )
{
  rc_t  rc = kOkRC;
  af_t  af;
  af_t* p  = &af;

  memset(&af,0,sizeof(af));

  if((rc = _open(p, fn, "r+b")) != kOkRC )
    goto errLabel;  

  if( p->info.srate != srate )
  {
    // change the sample rate
    p->info.srate = srate;

    // write the file header
    if((rc = _writeHdr(p)) != kOkRC )
      goto errLabel;
  }
  
 errLabel:
  if( p->fp != NULL )
    fclose(p->fp);
  
  return rc;
}



/// [example]

cw::rc_t cw::audiofile::test( const object_t* cfg )
{
 
  rc_t rc = kOkRC;
  const object_t* o;
  
  if((o = cfg->find("rpt")) != nullptr )
    rc = _testReport(o);

  if((o = cfg->find("mix")) != nullptr )
    rc = _testReport(o);

  
  /*
  switch( argc )
  {
    case 3:
      //_test(argv[2],&ctx->rpt);
      reportFn(argv[2],0,0,&ctx->rpt);
      break;
      
    case 4:
      {
        errno = 0;
        long srate =  strtol(argv[3], NULL, 10);
        if( srate == 0 && errno != 0 )
          rc = cwLogError(kInvalidArgRC,"Invalid sample rate argument to test().");
        else
          setSrate(argv[2],srate);                
      }
      break;

    case 8:
      {
        errno = 0;
        double   srate = strtod(argv[3],NULL);
        unsigned bits  = strtol(argv[4],NULL,10);
        double   hz    = strtod(argv[5],NULL);
        double   gain  = strtod(argv[6],NULL);
        double   secs  = strtod(argv[7],NULL);
        
        if( errno != 0 )
          rc = cwLogError(kInvalidArgRC,"Invalid arg. to test().");
        
        sine( ctx, argv[2], srate, bits,  hz, gain,  secs );
      }
      break;

    default:
      rc = cwLogError(kInvalidArgRC,"Invalid argument count to test().");
      break;
  }
  */
  return rc;
}


/// [example]
