[sink_wavwrite] check fseek and fwrite return values
[aubio.git] / src / io / source_apple_audio.c
index e5dede1..0ef60cd 100644 (file)
 
 */
 
-#ifdef __APPLE__
-#include "config.h"
 #include "aubio_priv.h"
+
+#ifdef HAVE_SOURCE_APPLE_AUDIO
+
 #include "fvec.h"
+#include "fmat.h"
 #include "io/source_apple_audio.h"
 
 // ExtAudioFileRef, AudioStreamBasicDescription, AudioBufferList, ...
 #define RT_BYTE3( a )      ( ((a) >> 16) & 0xff )
 #define RT_BYTE4( a )      ( ((a) >> 24) & 0xff )
 
-#define SHORT_TO_FLOAT(x) (smpl_t)(x * 3.0517578125e-05)
-
 struct _aubio_source_apple_audio_t {
   uint_t channels;
-  uint_t samplerate;
+  uint_t samplerate;          //< requested samplerate
+  uint_t source_samplerate;   //< actual source samplerate
   uint_t block_size;
 
   char_t *path;
@@ -45,39 +46,70 @@ struct _aubio_source_apple_audio_t {
   AudioBufferList bufferList;
 };
 
-extern int createAubioBufferList(AudioBufferList *bufferList, int channels, int segmentSize);
+extern int createAudioBufferList(AudioBufferList *bufferList, int channels, int max_source_samples);
 extern void freeAudioBufferList(AudioBufferList *bufferList);
-extern CFURLRef getURLFromPath(const char * path);
+extern CFURLRef createURLFromPath(const char * path);
+char_t *getPrintableOSStatusError(char_t *str, OSStatus error);
 
-aubio_source_apple_audio_t * new_aubio_source_apple_audio(char_t * path, uint_t samplerate, uint_t block_size)
+uint_t aubio_source_apple_audio_open (aubio_source_apple_audio_t *s, const char_t * path);
+
+aubio_source_apple_audio_t * new_aubio_source_apple_audio(const char_t * path, uint_t samplerate, uint_t block_size)
 {
   aubio_source_apple_audio_t * s = AUBIO_NEW(aubio_source_apple_audio_t);
 
-  s->path = path;
-  s->samplerate = samplerate;
+  if (path == NULL || strnlen(path, PATH_MAX) < 1) {
+    AUBIO_ERROR("source_apple_audio: Aborted opening null path\n");
+    goto beach;
+  }
+
+  if ( (sint_t)block_size <= 0 ) {
+    AUBIO_ERROR("source_apple_audio: Can not open %s with null or negative block_size %d\n",
+        path, block_size);
+    goto beach;
+  }
+
+  if ( (sint_t)samplerate < 0 ) {
+    AUBIO_ERROR("source_apple_audio: Can not open %s with negative samplerate %d\n",
+        path, samplerate);
+    goto beach;
+  }
+
   s->block_size = block_size;
-  s->channels = 1;
+  s->samplerate = samplerate;
 
+  if ( aubio_source_apple_audio_open ( s, path ) ) {
+    goto beach;
+  }
+  return s;
+
+beach:
+  del_aubio_source_apple_audio(s);
+  return NULL;
+}
+
+uint_t aubio_source_apple_audio_open (aubio_source_apple_audio_t *s, const char_t * path)
+{
   OSStatus err = noErr;
   UInt32 propSize;
 
-  AudioStreamBasicDescription clientFormat;
-  propSize = sizeof(clientFormat);
-  memset(&clientFormat, 0, sizeof(AudioStreamBasicDescription));
-  clientFormat.mFormatID         = kAudioFormatLinearPCM;
-  clientFormat.mSampleRate       = (Float64)(s->samplerate);
-  clientFormat.mFormatFlags      = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
-  clientFormat.mChannelsPerFrame = s->channels;
-  clientFormat.mBitsPerChannel   = sizeof(short) * 8;
-  clientFormat.mFramesPerPacket  = 1;
-  clientFormat.mBytesPerFrame    = clientFormat.mBitsPerChannel * clientFormat.mChannelsPerFrame / 8;
-  clientFormat.mBytesPerPacket   = clientFormat.mFramesPerPacket * clientFormat.mBytesPerFrame;
-  clientFormat.mReserved         = 0;
+  s->path = AUBIO_ARRAY(char_t, strnlen(path, PATH_MAX) + 1);
+  strncpy(s->path, path, strnlen(path, PATH_MAX) + 1);
 
   // open the resource url
-  CFURLRef fileURL = getURLFromPath(path);
+  CFURLRef fileURL = createURLFromPath(s->path);
   err = ExtAudioFileOpenURL(fileURL, &s->audioFile);
-  if (err) { AUBIO_ERR("error when trying to access %s, in ExtAudioFileOpenURL, %d\n", s->path, (int)err); goto beach;}
+  CFRelease(fileURL);
+  if (err == -43) {
+    AUBIO_ERR("source_apple_audio: Failed opening %s, "
+        "file not found, or no read access\n", s->path);
+    goto beach;
+  } else if (err) {
+    char_t errorstr[20];
+    AUBIO_ERR("source_apple_audio: Failed opening %s, "
+        "error in ExtAudioFileOpenURL (%s)\n", s->path,
+        getPrintableOSStatusError(errorstr, err));
+    goto beach;
+  }
 
   // create an empty AudioStreamBasicDescription
   AudioStreamBasicDescription fileFormat;
@@ -87,13 +119,42 @@ aubio_source_apple_audio_t * new_aubio_source_apple_audio(char_t * path, uint_t
   // fill it with the file description
   err = ExtAudioFileGetProperty(s->audioFile,
       kExtAudioFileProperty_FileDataFormat, &propSize, &fileFormat);
-  if (err) { AUBIO_ERROR("error in ExtAudioFileGetProperty, %d\n", (int)err); goto beach;}
+  if (err) {
+    char_t errorstr[20];
+    AUBIO_ERROR("source_apple_audio: Failed opening %s, "
+        "error in ExtAudioFileGetProperty (%s)\n", s->path,
+        getPrintableOSStatusError(errorstr, err));
+    goto beach;
+  }
+
+  if (s->samplerate == 0) {
+    s->samplerate = fileFormat.mSampleRate;
+    //AUBIO_DBG("sampling rate set to 0, automagically adjusting to %d\n", samplerate);
+  }
+
+  s->source_samplerate = fileFormat.mSampleRate;
+  s->channels = fileFormat.mChannelsPerFrame;
+
+  AudioStreamBasicDescription clientFormat;
+  propSize = sizeof(AudioStreamBasicDescription);
+  memset(&clientFormat, 0, sizeof(AudioStreamBasicDescription));
+  clientFormat.mFormatID         = kAudioFormatLinearPCM;
+  clientFormat.mSampleRate       = (Float64)(s->samplerate);
+  clientFormat.mFormatFlags      = kAudioFormatFlagIsFloat;
+  clientFormat.mChannelsPerFrame = s->channels;
+  clientFormat.mBitsPerChannel   = sizeof(smpl_t) * 8;
+  clientFormat.mFramesPerPacket  = 1;
+  clientFormat.mBytesPerFrame    = clientFormat.mBitsPerChannel * clientFormat.mChannelsPerFrame / 8;
+  clientFormat.mBytesPerPacket   = clientFormat.mFramesPerPacket * clientFormat.mBytesPerFrame;
 
   // set the client format description
   err = ExtAudioFileSetProperty(s->audioFile, kExtAudioFileProperty_ClientDataFormat,
       propSize, &clientFormat);
-  if (err) { AUBIO_ERROR("error in ExtAudioFileSetProperty, %d\n", (int)err); goto beach;}
-
+  if (err) {
+    char_t errorstr[20];
+    AUBIO_ERROR("source_apple_audio: Failed opening %s, "
+        "error in ExtAudioFileSetProperty (%s)\n", s->path,
+        getPrintableOSStatusError(errorstr, err));
 #if 0
   // print client and format descriptions
   AUBIO_DBG("Opened %s\n", s->path);
@@ -111,68 +172,191 @@ aubio_source_apple_audio_t * new_aubio_source_apple_audio(char_t * path, uint_t
   AUBIO_DBG("file/client Format.mBytesPerPacket   : %6d / %d\n",    (int)fileFormat.mBytesPerPacket  , (int)clientFormat.mBytesPerPacket);
   AUBIO_DBG("file/client Format.mReserved         : %6d / %d\n",    (int)fileFormat.mReserved        , (int)clientFormat.mReserved);
 #endif
+      goto beach;
+  }
 
-  // compute the size of the segments needed to read the input file
-  UInt32 samples = s->block_size * clientFormat.mChannelsPerFrame;
-  Float64 rateRatio = clientFormat.mSampleRate / fileFormat.mSampleRate;
-  uint_t segmentSize= (uint_t)(samples * rateRatio + .5);
-  if (rateRatio < 1.) {
-    segmentSize = (uint_t)(samples / rateRatio + .5);
-  } else if (rateRatio > 1.) {
-    AUBIO_WRN("up-sampling %s from %0.2fHz to %0.2fHz\n", s->path, fileFormat.mSampleRate, clientFormat.mSampleRate);
-  } else {
-    assert (segmentSize == samples );
-    //AUBIO_DBG("not resampling, segmentSize %d, block_size %d\n", segmentSize, s->block_size);
+  smpl_t ratio = s->source_samplerate * 1. / s->samplerate;
+  if (ratio < 1.) {
+    AUBIO_WRN("source_apple_audio: up-sampling %s from %0dHz to %0dHz\n",
+        s->path, s->source_samplerate, s->samplerate);
   }
 
   // allocate the AudioBufferList
-  if (createAubioBufferList(&s->bufferList, s->channels, segmentSize)) err = -1;
+  freeAudioBufferList(&s->bufferList);
+  if (createAudioBufferList(&s->bufferList, s->channels, s->block_size * s->channels)) {
+    AUBIO_ERR("source_apple_audio: failed creating bufferList\n");
+    goto beach;
+  }
 
-  return s;
 beach:
-  AUBIO_FREE(s);
-  return NULL;
+  return err;
 }
 
-void aubio_source_apple_audio_do(aubio_source_apple_audio_t *s, fvec_t * read_to, uint_t * read) {
-  UInt32 c, v, loadedPackets = s->block_size;
+static UInt32 aubio_source_apple_audio_read_frame(aubio_source_apple_audio_t *s)
+{
+  UInt32 loadedPackets = s->block_size;
   OSStatus err = ExtAudioFileRead(s->audioFile, &loadedPackets, &s->bufferList);
-  if (err) { AUBIO_ERROR("error in ExtAudioFileRead, %d\n", (int)err); goto beach;}
-
-  smpl_t *buf = read_to->data;
-
-  short *data = (short*)s->bufferList.mBuffers[0].mData;
-
-  if (buf) {
-      for (c = 0; c < s->channels; c++) {
-          for (v = 0; v < s->block_size; v++) {
-              if (v < loadedPackets) {
-                  buf[v * s->channels + c] =
-                      SHORT_TO_FLOAT(data[ v * s->channels + c]);
-              } else {
-                  buf[v * s->channels + c] = 0.f;
-              }
-          }
-      }
+  if (err) {
+    char_t errorstr[20];
+    AUBIO_ERROR("source_apple_audio: error while reading %s "
+        "with ExtAudioFileRead (%s)\n", s->path,
+        getPrintableOSStatusError(errorstr, err));
+  }
+  return loadedPackets;
+}
+
+void aubio_source_apple_audio_do(aubio_source_apple_audio_t *s, fvec_t * read_to,
+    uint_t * read) {
+  uint_t c, v;
+  UInt32 loadedPackets = aubio_source_apple_audio_read_frame(s);
+  smpl_t *data = (smpl_t*)s->bufferList.mBuffers[0].mData;
+
+  for (v = 0; v < loadedPackets; v++) {
+    read_to->data[v] = 0.;
+    for (c = 0; c < s->channels; c++) {
+      read_to->data[v] += data[ v * s->channels + c];
+    }
+    read_to->data[v] /= (smpl_t)s->channels;
+  }
+  // short read, fill with zeros
+  if (loadedPackets < s->block_size) {
+    for (v = loadedPackets; v < s->block_size; v++) {
+      read_to->data[v] = 0.;
+    }
   }
-  //if (loadedPackets < s->block_size) return EOF;
+
   *read = (uint_t)loadedPackets;
   return;
-beach:
-  *read = 0;
+}
+
+void aubio_source_apple_audio_do_multi(aubio_source_apple_audio_t *s, fmat_t * read_to, uint_t * read) {
+  uint_t c, v;
+  UInt32 loadedPackets = aubio_source_apple_audio_read_frame(s);
+  smpl_t *data = (smpl_t*)s->bufferList.mBuffers[0].mData;
+
+  for (v = 0; v < loadedPackets; v++) {
+    for (c = 0; c < read_to->height; c++) {
+      read_to->data[c][v] = data[ v * s->channels + c];
+    }
+  }
+  // if read_data has more channels than the file
+  if (read_to->height > s->channels) {
+    // copy last channel to all additional channels
+    for (v = 0; v < loadedPackets; v++) {
+      for (c = s->channels; c < read_to->height; c++) {
+        read_to->data[c][v] = data[ v * s->channels + (s->channels - 1)];
+      }
+    }
+  }
+  // short read, fill with zeros
+  if (loadedPackets < s->block_size) {
+    for (v = loadedPackets; v < s->block_size; v++) {
+      for (c = 0; c < read_to->height; c++) {
+        read_to->data[c][v] = 0.;
+      }
+    }
+  }
+
+  *read = (uint_t)loadedPackets;
   return;
 }
 
-void del_aubio_source_apple_audio(aubio_source_apple_audio_t * s){
+uint_t aubio_source_apple_audio_close (aubio_source_apple_audio_t *s)
+{
   OSStatus err = noErr;
-  if (!s || !s->audioFile) { return; }
+  if (!s->audioFile) { return AUBIO_OK; }
   err = ExtAudioFileDispose(s->audioFile);
-  if (err) AUBIO_ERROR("error in ExtAudioFileDispose, %d\n", (int)err);
   s->audioFile = NULL;
+  if (err) {
+    char_t errorstr[20];
+    AUBIO_ERROR("source_apple_audio: error while closing %s "
+        "in ExtAudioFileDispose (%s)\n", s->path,
+        getPrintableOSStatusError(errorstr, err));
+    return err;
+  }
+  return AUBIO_OK;
+}
+
+void del_aubio_source_apple_audio(aubio_source_apple_audio_t * s){
+  AUBIO_ASSERT(s);
+  aubio_source_apple_audio_close (s);
+  if (s->path) AUBIO_FREE(s->path);
   freeAudioBufferList(&s->bufferList);
   AUBIO_FREE(s);
-  return;
 }
 
-#endif /* __APPLE__ */
+uint_t aubio_source_apple_audio_seek (aubio_source_apple_audio_t * s, uint_t pos) {
+  OSStatus err = noErr;
+  if ((sint_t)pos < 0) {
+    AUBIO_ERROR("source_apple_audio: error while seeking in %s "
+        "(can not seek at negative position %d)\n",
+        s->path, pos);
+    err = -1;
+    goto beach;
+  }
+  // check if we are not seeking out of the file
+  uint_t fileLengthFrames = aubio_source_apple_audio_get_duration(s);
+  // compute position in the source file, before resampling
+  smpl_t ratio = s->source_samplerate * 1. / s->samplerate;
+  SInt64 resampled_pos = (SInt64)ROUND( pos * ratio );
+  if (resampled_pos > fileLengthFrames) {
+    AUBIO_ERR("source_apple_audio: trying to seek in %s at pos %d, "
+        "but file has only %d frames\n",
+        s->path, pos, (uint_t)(fileLengthFrames / ratio));
+    err = -1;
+    goto beach;
+  }
+  // after a short read, the bufferList size needs to resetted to prepare for a full read
+  AudioBufferList *bufferList = &s->bufferList;
+  bufferList->mBuffers[0].mDataByteSize = s->block_size * s->channels * sizeof (smpl_t);
+  // do the actual seek
+  err = ExtAudioFileSeek(s->audioFile, resampled_pos);
+  if (err) {
+    char_t errorstr[20];
+    AUBIO_ERROR("source_apple_audio: error while seeking %s at %d "
+        "in ExtAudioFileSeek (%s)\n", s->path, pos,
+        getPrintableOSStatusError(errorstr, err));
+  }
+#if 0
+  // check position after seek
+  {
+    SInt64 outFrameOffset = 0;
+    err = ExtAudioFileTell(s->audioFile, &outFrameOffset);
+    if (err) {
+      char_t errorstr[20];
+      AUBIO_ERROR("source_apple_audio: error while seeking %s at %d "
+          "in ExtAudioFileTell (%s)\n", s->path, pos,
+          getPrintableOSStatusError(errorstr, err));
+    }
+    AUBIO_DBG("source_apple_audio: asked seek at %d, tell got %d\n",
+        pos, (uint_t)(outFrameOffset / ratio + .5));
+  }
+#endif
+beach:
+  return err;
+}
+
+uint_t aubio_source_apple_audio_get_samplerate(const aubio_source_apple_audio_t * s) {
+  return s->samplerate;
+}
+
+uint_t aubio_source_apple_audio_get_channels(const aubio_source_apple_audio_t * s) {
+  return s->channels;
+}
+
+uint_t aubio_source_apple_audio_get_duration(const aubio_source_apple_audio_t * s) {
+  SInt64 fileLengthFrames = 0;
+  UInt32 propSize = sizeof(fileLengthFrames);
+  OSStatus err = ExtAudioFileGetProperty(s->audioFile,
+      kExtAudioFileProperty_FileLengthFrames, &propSize, &fileLengthFrames);
+  if (err) {
+    char_t errorstr[20];
+    AUBIO_ERROR("source_apple_audio: Failed getting %s duration, "
+        "error in ExtAudioFileGetProperty (%s)\n", s->path,
+        getPrintableOSStatusError(errorstr, err));
+    return err;
+  }
+  return (uint_t)fileLengthFrames;
+}
+
+#endif /* HAVE_SOURCE_APPLE_AUDIO */