Copyright (C) Kevin Larke 2009-2020

This file is part of libcm.

libcm is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

libcm is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

See the GNU General Public License distributed with the libcm package or look here: .


cmAudioFile : Read and write AIFF and WAV audio files.

This class supports reading uncompressed AIFF and WAV files and writing uncompressed AIFF files. The reading and writing routines are known to work with 8,16,24, and 32 bit integer sample formats.



#ifndef cmAudioFile_MAX_FRAME_READ_CNT
// Maximum number of samples which will be read in one call to fread().
// This value is only significant in that an internal buffer is created on the stack
// whose size must be limited to prevent stack overflows.
#define cmAudioFile_MAX_FRAME_READ_CNT (8192) 
#endif


// Audio file result codes.
enum
{
  kOkAfRC = 0,
  kOpenFailAfRC,
  kReadFailAfRC,
  kWriteFailAfRC,
  kSeekFailAfRC,
  kCloseFailAfRC,
  kNotAiffAfRC,
  kInvalidBitWidthAfRC,
  kInvalidFileModeAfRC,
  kInvalidHandleAfRC,
  kInvalidChCountAfRC,
  kRptFileFailAfRC,
  kUnknownErrAfRC
};

// Informational flags used by audioFileInfo
enum
{
  kAiffAfFl        = 0x01,     // this is an AIFF file 
  kWavAfFl         = 0x02,     // this is a WAV file 
  kSwapAfFl        = 0x04,     // file header bytes must be swapped
  kAifcAfFl        = 0x08,     // this is an AIFC file
  kSwapSamplesAfFl = 0x10      // file sample bytes must be swapped
};


// Constants
enum
{
  kAudioFileLabelCharCnt = 256,
  
  kAfBextDescN       = 256,
  kAfBextOriginN     = 32,
  kAfBextOriginRefN  = 32,
  kAfBextOriginDateN = 10,
  kAfBextOriginTimeN = 8
};

// Aiff marker record
typedef struct
{
  unsigned    id;
  unsigned    frameIdx;
  char        label[kAudioFileLabelCharCnt];
} cmAudioFileMarker_t;

// Broadcast WAV header record As used by ProTools audio files. See http://en.wikipedia.org/wiki/Broadcast_Wave_Format
// When generated from Protools the timeRefLow/timeRefHigh values appear to actually refer
// to the position on the Protools time-line rather than the wall clock time.
typedef struct
{
  char     desc[      kAfBextDescN       + 1 ];
  char     origin[    kAfBextOriginN     + 1 ];
  char     originRef[ kAfBextOriginRefN  + 1 ];
  char     originDate[kAfBextOriginDateN + 1 ];
  char     originTime[kAfBextOriginTimeN + 1 ];
  unsigned timeRefLow;    // sample count since midnight low word
  unsigned timeRefHigh;   // sample count since midnight high word
} cmAudioFileBext_t;

// Audio file information record used by audioFileNew and audioFileOpen
typedef struct 
{
  unsigned             bits;         // bits per sample
  unsigned             chCnt;        // count of audio file channels
  double               srate;        // audio file sample rate in samples per second
  unsigned             frameCnt;     // total number of sample frames in the audio file
  unsigned             flags;        // informational flags 
  unsigned             markerCnt;    // count of markers in markerArray
  cmAudioFileMarker_t* markerArray;  // array of markers 
  cmAudioFileBext_t    bextRecd;     // only used with Broadcast WAV files
} cmAudioFileInfo_t;



typedef cmHandle_t cmAudioFileH_t;     // opaque audio file handle 
extern cmAudioFileH_t cmNullAudioFileH;   // NULL audio file handle

// Create an audio file handle and optionally use the handle to open an audio file.
//
//   fn         The audio file name to open or NULL to create the audio file handle without opening the file.
//   infoPtr    A pointer to an audioFileInfo record to be filled when the file is open or NULL to ignore.
//   rcPtr      A pointer to a result code to be set in the event of a runtime error or NULL to ignore.
//   rpt        A pointer to a cmRpt_t object which error messages from this class will be directed to.
//  Returns  cmAudioFileH_t A new audio file handle.
//
cmAudioFileH_t cmAudioFileNewOpen( const cmChar_t* fn, cmAudioFileInfo_t* infoPtr, cmRC_t* rcPtr, cmRpt_t* rpt ); 

// Open an audio file for writing
cmAudioFileH_t cmAudioFileNewCreate( const cmChar_t* fn, double srate, unsigned bits, unsigned chCnt, cmRC_t* rcPtr, cmRpt_t* rpt );


// Open an audio file for reading using a handle returned from an earlier call to audioFileNewXXX().
//
//   h          A file handle returned from and earlier call to cmAudioFileNewOpen() or cmAudioFileNewCreate().
//   fn         The audio file name to open or NULL to create the audio file handle without opening the file.
//   infoPtr    A pointer to an audioFileInfo record to be filled when the file is open or NULL to ignore.
// Returns an cmRC_t value indicating the success (kOkAfRC) or failure of the call.
//
// If the audio file handle 'h' refers to an open file then it is automatically closed prior to being
// reopened with the new file.
cmRC_t     cmAudioFileOpen(       cmAudioFileH_t h, const cmChar_t* fn, cmAudioFileInfo_t* infoPtr );

// Open an audio file for writing.  The type of the audio file, AIF or WAV
// is determined by the file name extension.
cmRC_t     cmAudioFileCreate(     
cmAudioFileH_t h,     // Handle returned from an earlier call to cmAudioFileNewCreate() or cmAudioFileNewOpen().
const cmChar_t* fn,   // File name of the new file.
double srate,         // Sample rate of the new file.
unsigned bits,        // Sample word width for the new file in bits (must be 8,16,24 or 32).
unsigned chCnt        // Audio channel count for the new file.
);

// Close a the file associated with handle 'h' but do not release the handle.
// If the file was opened for writing (cmAudioFileCreate()) then this function will
// write the file header prior to closing the file.
cmRC_t     cmAudioFileClose(      cmAudioFileH_t* h );

// Close the file associated with handle 'h' (via an internal call to 
// cmAudioFileClose()) and release the handle and any resources
// associated with it.  This is the complement to cmAudioFileOpen/Create().
cmRC_t     cmAudioFileDelete(     cmAudioFileH_t* h );

// Return true if the handle is not closed or deleted.
bool       cmAudioFileIsValid(    cmAudioFileH_t h );

// Return true if the handle is open.
bool       cmAudioFileIsOpen(     cmAudioFileH_t h );

// Return true if the current file position is at the end of the file.
bool       cmAudioFileIsEOF(      cmAudioFileH_t h );

// Return the current file position as a frame index.
unsigned   cmAudioFileTell(       cmAudioFileH_t h );

// Set the current file position as an offset from the first frame.
cmRC_t     cmAudioFileSeek(       cmAudioFileH_t h, unsigned frmIdx );

// Sample Reading Functions.
//
// Fill a user suppled buffer with up to frmCnt samples.
// If less than frmCnt samples are available at the specified audio file location then the unused
// buffer space is set to zero. Check *actualFrmCntPtr for the count of samples actually available
// in the return buffer.  Functions which do not include a begFrmIdx argument begin reading from
// the current file location (see cmAudioFileSeek()). The buf argument is always a pointer to an
// array of pointers of length chCnt.  Each channel buffer specified in buf[] must contain at least
// frmCnt samples.
//
// 
//  h               An audio file handle returned from an earlier call to audioFileNew()
//  fn              The name of the audio file to read.
//  begFrmIdx       The frame index of the first sample to read. Functions that do not use this parameter begin reading at the current file location (See cmAudioFileTell()).
//  frmCnt          The number of samples allocated in buf.
//  chIdx           The index of the first channel to read.
//  chCnt           The count of channels to read.
//  buf             An array containing chCnt pointers to arrays of frmCnt samples.
//  actualFrmCntPtr The number of frames actually written to the return buffer (ignored if NULL)

cmRC_t     cmAudioFileReadInt(    cmAudioFileH_t h, unsigned frmCnt, unsigned chIdx, unsigned chCnt, int**    buf, unsigned* actualFrmCntPtr );
cmRC_t     cmAudioFileReadFloat(  cmAudioFileH_t h, unsigned frmCnt, unsigned chIdx, unsigned chCnt, float**  buf, unsigned* actualFrmCntPtr );
cmRC_t     cmAudioFileReadDouble( cmAudioFileH_t h, unsigned frmCnt, unsigned chIdx, unsigned chCnt, double** buf, unsigned* actualFrmCntPtr );

cmRC_t     cmAudioFileGetInt(    const char* fn, unsigned begFrmIdx, unsigned frmCnt, unsigned chIdx, unsigned chCnt, int**    buf, unsigned* actualFrmCntPtr, cmAudioFileInfo_t* afInfoPtr, cmRpt_t* rpt );
cmRC_t     cmAudioFileGetFloat(  const char* fn, unsigned begFrmIdx, unsigned frmCnt, unsigned chIdx, unsigned chCnt, float**  buf, unsigned* actualFrmCntPtr, cmAudioFileInfo_t* afInfoPtr, cmRpt_t* rpt );
cmRC_t     cmAudioFileGetDouble( const char* fn, unsigned begFrmIdx, unsigned frmCnt, unsigned chIdx, unsigned chCnt, double** buf, unsigned* actualFrmCntPtr, cmAudioFileInfo_t* afInfoPtr, cmRpt_t* rpt );

// Sum the returned samples into the output buffer.
cmRC_t     cmAudioFileReadSumInt(    cmAudioFileH_t h, unsigned frmCnt, unsigned chIdx, unsigned chCnt, int**    buf, unsigned* actualFrmCntPtr );
cmRC_t     cmAudioFileReadSumFloat(  cmAudioFileH_t h, unsigned frmCnt, unsigned chIdx, unsigned chCnt, float**  buf, unsigned* actualFrmCntPtr );
cmRC_t     cmAudioFileReadSumDouble( cmAudioFileH_t h, unsigned frmCnt, unsigned chIdx, unsigned chCnt, double** buf, unsigned* actualFrmCntPtr );

cmRC_t     cmAudioFileGetSumInt(    const char* fn, unsigned begFrmIdx, unsigned frmCnt, unsigned chIdx, unsigned chCnt, int**    buf, unsigned* actualFrmCntPtr, cmAudioFileInfo_t* afInfoPtr, cmRpt_t* rpt );
cmRC_t     cmAudioFileGetSumFloat(  const char* fn, unsigned begFrmIdx, unsigned frmCnt, unsigned chIdx, unsigned chCnt, float**  buf, unsigned* actualFrmCntPtr, cmAudioFileInfo_t* afInfoPtr, cmRpt_t* rpt );
cmRC_t     cmAudioFileGetSumDouble( const char* fn, unsigned begFrmIdx, unsigned frmCnt, unsigned chIdx, unsigned chCnt, double** buf, unsigned* actualFrmCntPtr, cmAudioFileInfo_t* afInfoPtr, cmRpt_t* rpt );

// Sample Writing Functions
cmRC_t    cmAudioFileWriteInt(    cmAudioFileH_t h, unsigned frmCnt, unsigned chCnt, int**    bufPtrPtr );
cmRC_t    cmAudioFileWriteFloat(  cmAudioFileH_t h, unsigned frmCnt, unsigned chCnt, float**  bufPtrPtr );
cmRC_t    cmAudioFileWriteDouble( cmAudioFileH_t h, unsigned frmCnt, unsigned chCnt, double** bufPtrPtr );

cmRC_t    cmAudioFileWriteFileInt(    const char* fn, double srate, unsigned bit, unsigned frmCnt, unsigned chCnt, int**    bufPtrPtr, cmRpt_t* rpt );
cmRC_t    cmAudioFileWriteFileFloat(  const char* fn, double srate, unsigned bit, unsigned frmCnt, unsigned chCnt, float**  bufPtrPtr, cmRpt_t* rpt );
cmRC_t    cmAudioFileWriteFileDouble( const char* fn, double srate, unsigned bit, unsigned frmCnt, unsigned chCnt, double** bufPtrPtr, cmRpt_t* rpt );



// Alias the cmSample_t and cmReal_t sample reading and writing functions to the appropriate
// type based on #CM_FLOAT_SMP and #CM_FLOAT_REAL.

#if CM_FLOAT_SMP == 1

#define cmAudioFileReadSample      cmAudioFileReadFloat
#define cmAudioFileReadSumSample   cmAudioFileReadSumFloat
#define cmAudioFileGetSample       cmAudioFileGetFloat
#define cmAudioFileGetSumSample    cmAudioFileGetSumFloat
#define cmAudioFileWriteSample     cmAudioFileWriteFloat
#define cmAudioFileWriteFileSample cmAudioFileWriteFileFloat

#else

#define cmAudioFileReadSample      cmAudioFileReadDouble
#define cmAudioFileReadSumSample   cmAudioFileReadSumDouble
#define cmAudioFileGetSample       cmAudioFileGetDouble
#define cmAudioFileGetSumSample    cmAudioFileGetSumDouble
#define cmAudioFileWriteSample     cmAudioFileWriteDouble
#define cmAudioFileWriteFileSample cmAudioFileWriteFileDouble

#endif

#if CM_FLOAT_REAL == 1

#define cmAudioFileReadReal      cmAudioFileReadFloat
#define cmAudioFileReadSumReal   cmAudioFileReadSumFloat
#define cmAudioFileGetReal       cmAudioFileGetFloat
#define cmAudioFileGetSumReal    cmAudioFileGetSumFloat
#define cmAudioFileWriteReal     cmAudioFileWriteFloat
#define cmAudioFileWriteFileReal cmAudioFileWriteFileFloat

#else

#define cmAudioFileReadReal      cmAudioFileReadDouble
#define cmAudioFileReadSumReal   cmAudioFileReadSumDouble
#define cmAudioFileGetReal       cmAudioFileGetDouble
#define cmAudioFileGetSumReal    cmAudioFileGetSumDouble
#define cmAudioFileWriteReal     cmAudioFileWriteDouble
#define cmAudioFileWriteFileReal cmAudioFileWriteFileDouble

#endif


// Scan an entire audio file and return the minimum, maximum and mean sample value.
// On error *minPtr, *maxPtr, and *meanPtr are set to -acSample_MAX, cmSample_MAX, and 0 respectively
cmRC_t     cmAudioFileMinMaxMean( cmAudioFileH_t h, unsigned chIdx, cmSample_t* minPtr, cmSample_t* maxPtr, cmSample_t* meanPtr );
cmRC_t     cmAudioFileMinMaxMeanFn( const cmChar_t* fn, unsigned chIdx, cmSample_t* minPtr, cmSample_t* maxPtr, cmSample_t* meanPtr, cmRpt_t* rpt );

// Return the file name associated with a audio file handle.
const cmChar_t* cmAudioFileName( cmAudioFileH_t h );

// Given an error code return the associated message.
const char* cmAudioFileErrorMsg( unsigned rc );

// Return the cmAudioFileInfo_t record associated with a file.
cmRC_t     cmAudioFileGetInfo(   const cmChar_t* fn, cmAudioFileInfo_t* infoPtr, cmRpt_t* rpt );

// Print the cmAudioFileInfo_t to a file.
void       cmAudioFilePrintInfo( const cmAudioFileInfo_t* infoPtr, cmRpt_t* );

cmRC_t     cmAudioFileReportInfo( cmCtx_t* ctx, const cmChar_t* audioFn, const cmChar_t* rptFn );

// Print the file header information and frmCnt sample values beginning at frame index frmIdx.
cmRC_t     cmAudioFileReport(   cmAudioFileH_t h,  cmRpt_t* rpt, unsigned frmIdx, unsigned frmCnt );

// Print the file header information and  frmCnt sample values beginning at frame index frmIdx.
cmRC_t     cmAudioFileReportFn( const cmChar_t* fn, unsigned frmIdx, unsigned frmCnt, cmRpt_t* rpt );

// Change the sample rate value in the header.  Note that this function does not resample the audio
// signal it simply changes the value of the sample rate in the header.
cmRC_t     cmAudioFileSetSrate( const cmChar_t* audioFn, unsigned srate );

// Generate a sine tone and write it to a file.
cmRC_t     cmAudioFileSine( cmCtx_t* ctx, const cmChar_t* fn, double srate, unsigned bits, double hz, double gain, double secs );


// Testing and example routine for functions in cmAudioFile.h.
// Also see cmProcTest.c cmAudioFileReadWriteTest()
void       cmAudioFileTest( cmCtx_t* ctx, int argc, const char* argv[] );