

// (note) ALSA async callbacks use signals. signals are somewhat parallel to pthreads
//        and the signalcode will run in the current thread, possibly causing deadlocks.
//        to avoid this, the open() method creates a new thread, which sigmask is set to
//        allow only the ALSA signal.


#include <stdio.h>
#include <string.h>
#include <math.h>

#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <dlfcn.h>

#include "tkportaudio.h"

#include "PaStream.h"


#include <sys/time.h>

#ifdef YAC_LINUX
#include <syscall.h>
#endif


static sUI async_cbk_count = 0;



#if 0
static struct timeval tv_last;
static sF64 GetMilliSecondsDouble(void) {

   struct timeval tv;
   ::gettimeofday(&tv, NULL);

   sF32 msDelta = (sF64) ((tv.tv_sec-tv_last.tv_sec)*1000 + ((tv.tv_usec-tv_last.tv_usec) / 1000.0));

   tv_last = tv;

   return msDelta;
}
#endif


#ifdef PANDORA_LOWLATENCY_HACK
typedef int (*snd_pcm_hw_set_hack_params_fxn_t)(snd_pcm_t *pcm, unsigned int frames_min, unsigned int frames_max);

static sBool loc_perform_hack(snd_pcm_t *handle, sUI _minBuf, sUI _maxBuf)
{
   // by Grazvydas Ignotas ("notaz")
	snd_pcm_hw_set_hack_params_fxn_t snd_pcm_hw_set_hack_params;
	int ret;
   void *dlHandle = NULL;

   // return YAC_FALSE;

   // dlHandle = dlopen("/usr/lib/libasound.so.2", RTLD_LAZY);

   // if(NULL != dlHandle)
   {
      snd_pcm_hw_set_hack_params = (snd_pcm_hw_set_hack_params_fxn_t) dlsym(dlHandle, "snd_pcm_hw_set_hack_params");
      
      if(NULL != snd_pcm_hw_set_hack_params)
      {
         ret = snd_pcm_hw_set_hack_params(handle, _minBuf, _maxBuf);
         
         yac_host->printf("[dbg] PaStream<alsa>: set hack params: %d(\"%s\")\n", ret, strerror(-ret));

         return (0 == ret);
      }
      else
      {
         yac_host->printf("[~~~] PaStream<alsa>: no pandora hack in libasound.\n");
      }

      // dlclose(dlHandle);
   }
   // else
   // {
   //    yac_host->printf("[~~~] PaStream<alsa>: failed to dlopen() libasound.\n");
   // }

   return YAC_FALSE;
}
#endif /* PANDORA_LOWLATENCY_HACK */



static int stream_callback(const void *  input, 
                           void *        output,
                           unsigned long frameCount,
                           _PaStream *   st
                           )
{
   YAC_FloatArray *ip = st->input_buf;


#if 0
   ip->num_elements = frameCount * st->num_input_channels;

   ////printf("xxx numin=%d framecount=%d\n", st->num_input_channels, frameCount);

   // Convert input samples to float format
   {
      sUI i;
      sUI numElem = ip->num_elements;
      sF32 *d = ip->elements;
      
      switch(99)
      {
         case 99:
         {
            // Silence
            sF32 *s = (sF32*) input;
            for(i=0; i<numElem; i++)
            {
               *d++ = 0.0f;
            }
         }
         break;
            
         case paFloat32:
         {
            sF32 *s = (sF32*) input;
            for(i=0; i<numElem; i++)
            {
               *d++ = *s++;
            }
         }
         break;

         case paInt32:
         {
            sSI *s = (sSI*) input;
            for(i=0; i<numElem; i++)
            {
               *d++ = (*s++ / 2147483647.0f);
            }
         }
         break;

         case paInt24: 
         {
            sU8 *s = (sU8*) input;
            for(i=0; i<numElem; i++)
            {
               sUI v = ((sUI)s[0]) | ((sUI)(s[1]<<8)) | ((sUI)(s[1]<<16));
               if(v & 0x800000)
                  v |= 0xFF000000;
               *d++ = ((*(sSI*)&v) / 8388607.0f);
               s += 3;
            }
         }
         break;

         case paInt8:
         {
            sS8 *s = (sS8*) input;
            for(i=0; i<numElem; i++)
            {
               *d++ = (*s++) / 127.0f;
            }
         }
         break;

         case paInt16:
         {
            sS16 *s = (sS16*) input;
            for(i=0; i<numElem; i++)
            {
               *d++ = (*s++) / 32767.0f;
            }
         }
         break;
            
         case paUInt8:
         {
            sU8 *s = (sU8*) input;
            for(i=0; i<numElem; i++)
            {
               *d++ = (((sSI)(*s++))-128) / 127.0f;
            }
         }
         break;
      } // switch sampleformat
   }
#else

   // sample input not implemented, yet
   ip->num_elements = 0;

#endif // 0   


   YAC_FloatArray *op = st->output_buf;
   op->num_elements = frameCount * PAR_CHANNELS;
   // fill output buffer with "silence"
   {
      sUI i;
      sUI numElem = op->num_elements;
      sF32 *d = op->elements;
      for(i=0; i<numElem; i++)
      {
         *d++ = 0.0f;
      }
   }

   // Call script fxn to fill output buffer
   YAC_Value args[4];
   args[0].initObject(st, 0);
   args[1].initObject(ip, 0);
   args[2].initObject(op, 0);
   args[3].initInt((sSI)frameCount);
   yac_host->yacEvalFunction(st->stream_cbk_context, st->stream_cbk, 4, args);

   // Convert outputbuffer to actual sampleformat
   {
      sUI i;
      sUI numElem = op->num_elements;
      sF32 *s = op->elements;

#define Dclipv sF32 v = *s++; if(v>1)v=1;else if(v<-1)v=-1      
      switch(st->sample_format)
      {
         case paFloat32:
         {
            sF32 *d = (sF32*) output;
            for(i=0; i<numElem; i++)
            {
               Dclipv;
               *d++ = *s++;
            }
         }
         break;

         case paInt32:
         {
            sSI *d = (sSI*) output;
            for(i=0; i<numElem; i++)
            {
               Dclipv;
               *d++ = (sSI) (v * 2147483647.0f);
            }
         }
         break;

         case paInt24:
         {
            sU8 *d = (sU8*) output;
            for(i=0; i<numElem; i++)
            {
               Dclipv;
               sUI vi = (sUI) (v * 8388607.0f);
               d[0] = (vi&255);
               d[1] = ((vi>>8)&255);
               d[2] = ((vi>>16)&255);
               d += 3;
            }
         }
         break;

         case paInt16:
         {
            sS16 *d = (sS16*) output;
            for(i=0; i<numElem; i++)
            {
               Dclipv;
               *d++ = (sS16) (v * 32767.0f);
            }
         }
         break;

         case paInt8:
         {
            sS8 *d = (sS8*) output;
            for(i=0; i<numElem; i++)
            {
               Dclipv;
               *d++ = (sS8) ( v * 127.0f );
            }
         }
         break;
            
         case paUInt8:
         {
            sU8 *d = (sU8*) output;
            for(i=0; i<numElem; i++)
            {
               Dclipv;
               *d++ = (sU8) ( v * 127.0f + 127.0f );
            }
         }
         break;
      } // switch sampleformat
   }

   return paContinue;
}


static void *pastream__threadentry(void *_userData) {

   _PaStream *st = (_PaStream*) _userData;
   int err;

#ifdef YAC_LINUX
   yac_host->printf("[dbg] PaStream::threadentry: threadId=" YAC_PRINTF_PTRPREFIX "%p LWPid=%d\n", pthread_self(), syscall(SYS_gettid));
#endif

   // increase thread priority
   {
      struct sched_param param;

      memset((void*)&param, 0, sizeof(struct sched_param));
      param.sched_priority = 40;//_priority;
      ////pthread_setschedparam(st->pthread_id, SCHED_OTHER, &param);
      err = pthread_setschedparam(st->pthread_id, SCHED_FIFO, &param);
      if(0 != err)
      {
         printf("[---] PaStream: pthread_setschedparam() failed, errno=%d(\"%s\")\n", errno, strerror(errno));
      }
   }

   // Allow only ALSA I/O signal
   //  (note) there must not be any other thread that allows this signal!
   //  (note) the TKS runtime disallows SIGIO by default
   sigset_t ss;
   sigfillset(&ss);
   pthread_sigmask(SIG_BLOCK, &ss, NULL); // block all signals..
   sigemptyset(&ss);
   sigaddset(&ss, SIGIO);
   pthread_sigmask(SIG_UNBLOCK, &ss, NULL); // ..except for SIGIO

   st->b_running = YAC_TRUE;

   while(st->b_running)
   {
      //printf("xxx audio tick\n");
      sUI sleepLeft = sleep(1); // (note) sleep() gets interrupted by SIGIO from alsa kernel module

      if(0 == sleepLeft)
      {
         if(st->b_running)
         {
            snd_pcm_state_t pcmState;

            pcmState = snd_pcm_state(st->handle);

            if(SND_PCM_STATE_RUNNING == pcmState)
            {
               yac_host->printf("[dbg] PaStream: pcmState=SND_PCM_STATE_RUNNING, async_cbk_count=%u avgAvail=%.3f\n",
                                async_cbk_count,
                                st->dbg_avg_avail
                                );
            }
            else if(SND_PCM_STATE_XRUN == pcmState)
            {
               yac_host->printf("[~~~] PaStream: detected SND_PCM_STATE_XRUN, (re-)preparing stream..\n");

               err = snd_pcm_prepare(st->handle);

               if(err < 0)
               {
                  yac_host->printf("[~~~] PaStream: snd_pcm_prepare() failed. err=%d(\"%s\").\n",
                                   err,
                                   snd_strerror(err)
                                   );
               }
            }
            else if(SND_PCM_STATE_PREPARED == pcmState)
            {
               yac_host->printf("[~~~] PaStream: detected SND_PCM_STATE_PREPARED, (re-)starting stream..\n");

               if(st->b_hw)
               {
                  memset(st->out_buf, 0, TKPORTAUDIO_MAX_BUFSIZE);
                  for(sUI i=0; i<st->num_periods; i++)
                  {
                     snd_pcm_writei(st->handle, st->out_buf, st->period_size);
                  }
               }

               err = snd_pcm_start(st->handle);

               if(err < 0)
               {
                  yac_host->printf("[~~~] PaStream: snd_pcm_start() failed. err=%d(\"%s\").\n",
                                   err,
                                   snd_strerror(err)
                                   );
               }

            }
            else if(SND_PCM_STATE_PAUSED == pcmState)
            {
               if(st->b_active)
               {
                  err = snd_pcm_start(st->handle);

                  if(err < 0)
                  {
                     yac_host->printf("[~~~] PaStream: snd_pcm_start() failed. err=%d(\"%s\").\n",
                                      err,
                                      snd_strerror(err)
                                      );
                  }
               }
            }
            else
            {
               yac_host->printf("[dbg] PaStream: pcmState=%d, async_cbk_count=%u\n", pcmState, async_cbk_count);
            }
         }
      }
      else
      {
         if(0 == (async_cbk_count & 1023))
         {
            yac_host->printf("[trc] PaStream: async_cbk_count=%u avgAvail=%.3f\n", async_cbk_count, st->dbg_avg_avail);
         }
      }
   }

   return NULL;
}

static void async__cbk(snd_async_handler_t *ahandler) {

   // (note) this callback is always invoked in the context of the thread created by open()

   _PaStream *st = (_PaStream*) snd_async_handler_get_callback_private(ahandler);

   snd_pcm_sframes_t frames;
   
   short *buf = (short*) st->out_buf;

#if 0
   static sBool bFirstFrag = 1;

   if(bFirstFrag)
   {
      struct sched_param param;
      int err;
      int policy;

      bFirstFrag = 0;

      memset((void*)&param, 0, sizeof(struct sched_param));
      err = pthread_getschedparam(pthread_self(), &policy, &param);
      printf("xxx async__cbk: param.sched_priority=%d policy=%d (SCHED_FIFO=%d) err=%d\n", param.sched_priority, policy, SCHED_FIFO, err);
      // ==> ok, prio=40, policy=1=SCHED_FIFO
   }
#endif

   if(st->b_active)
   {
      sSI avail;

      //printf("xxx PaStream::async__cbk: avail=%d\n", avail);

      // // if(avail >= (sSI)st->period_size)
      // {

      st->b_processing_cbk = 1;

      avail = st->pcmAvailUpdateRecover();

      st->dbg_avg_avail = (st->dbg_avg_avail + avail) * 0.5f;

      // printf("xxx begin avail=%d b_active=%d\n", avail, st->b_active);

      while(st->b_active)
         //if(avail >= (sSI)st->period_size)
      {

         async_cbk_count++;

         // if(0 == (async_cbk_count&31))
         // {
         //    printf("xxx async__cbk ENTER. async_cbk_count=%u avail=%d\n", async_cbk_count, avail); 
         // }

         if(avail > 0)
         {
            while(avail > 0)
            {
               sUI curNumFrames;

               if(avail >= (sSI)st->period_size)
               {
                  curNumFrames = st->period_size;
               }
               else
               {
                  curNumFrames = avail;
               }

               // if(0 == st->num_left)
               {
                  // printf("xxx async__cbk: stream_callback curNumFrames=%u\n", curNumFrames);
                  // Calc sample data
                  stream_callback(NULL, buf, 
                                  curNumFrames,///st->period_size,
                                  st
                                  );

                  // st->num_left = st->period_size;
               }

               // if(st->num_left >= (sUI)avail)
               // {
               //    curNumFrames = (sUI)avail;
               // }
               // else
               // {
               //    curNumFrames = st->num_left;
               // }
         
//             if(bFirstFrag)
//             {
//                sF32 dt = GetMilliSecondsDouble();
//                printf("audio dt=%f avail=%u \n", dt, avail);
//                bFirstFrag = 0;
//             }
         
               // Write fragment
               frames = snd_pcm_writei(st->handle,
                                       // buf + (PAR_CHANNELS * (st->period_size - st->num_left)),
                                       buf,
                                       curNumFrames
                                       );

               if(frames < 0)
               {
                  frames = snd_pcm_recover(st->handle, frames/*err*/, 1/*silent*/);
               }

               if(frames < 0)
               {
                  printf("snd_pcm_recover<writei> failed: err=%d\n", (int)frames);
               }

               // if (frames > 0 && frames < (long)curNumFrames)
               //    printf("Short write (expected %li, wrote %li)\n", (long)curNumFrames, frames);

               avail -= curNumFrames;

#if 0
               if(!st->b_hw)
               {
                  if(avail > st->period_size)
                  {
                     st->b_processing_cbk = 0;
                     return;
                  }
               }
#endif

               // st->num_left -= curNumFrames;

               // if(0 == avail)
               // {
               //    avail = (sSI)snd_pcm_avail(st->handle);
               // }

            } // while avail
         } // if avail
         else
         {
            // No buffer space available
            break;
         }
         
      } // while active

      // //avail = st->pcmAvailUpdateRecover();
      avail = (sSI)snd_pcm_avail(st->handle);
      // printf("xxx end avail=%d\n", avail);

      st->b_processing_cbk = 0;

   } // if active

//    printf("xxx async__cbk LEAVE\n");

}



_PaStream::_PaStream(void) {

   stream_cbk         = NULL;
   stream_cbk_context = NULL;

   b_running = YAC_FALSE;
   b_active  = YAC_FALSE;
   b_processing_cbk = YAC_FALSE;
   handle    = NULL;
   b_hw      = YAC_FALSE;

   input_buf  = NULL;
   output_buf = NULL;

   sample_rate = 0;

   dbg_avg_avail = 0.0f;
}

_PaStream::~_PaStream() {
   _abort();
   _close();
}

sBool _PaStream::hwParamsSet(snd_pcm_hw_params_t *hwparams, int _line) {
   int err;

   if((err = snd_pcm_hw_params(handle,
                               hwparams
                               )) < 0)
   { 
      yac_host->printf("[---] PaStream<alsa>:%d: snd_pcm_hw_params() failed. err=%d(\"%s\")\n",
                       _line,
                       err,
                       snd_strerror(err)
                       );
      snd_pcm_close(handle);
      handle = NULL;
      return YAC_FALSE;
   }
   else
   {
      return YAC_TRUE;
   }
}

sBool _PaStream::hwParamsGet(snd_pcm_hw_params_t *hwparams, int _line) {
   int err;

   if((err = snd_pcm_hw_params_current(handle,
                                       hwparams
                                       )) < 0)
   { 
      yac_host->printf("[---] PaStream<alsa>:%d: snd_pcm_hw_params_current() failed. err=%d(\"%s\")\n",
                       _line,
                       err,
                       snd_strerror(err)
                       );
      snd_pcm_close(handle);
      handle = NULL;
      return YAC_FALSE;
   }
   else
   {
      return YAC_TRUE;
   }
}

sBool _PaStream::initStream2(void *_cbk,
                            _PaStreamParameters *_inParams,
                            _PaStreamParameters *_outParams,
                             sUI _sampleRate,
                             sUI _periodSize,
                             sUI _bufferSize
                           ) 
{

//    if(PAR_FORMAT != _outParams->sample_format)
//    {
//       printf("[~~~] PaStream<alsa>::initStream: sample format mismatch (%d != %d). Forcing 16bit..\n", 
//              _outParams->sample_format,
//              PAR_FORMAT
//              );
// //       return 0;
//    }

   if(PAR_CHANNELS != _outParams->channel_count)
   {
      printf("[---] PaStream<alsa>::initStream: channel_count mismatch (%d != %d)\n", 
             _outParams->channel_count,
             PAR_CHANNELS 
             );
      return 0;
   }


   // Create script context
   stream_cbk         = _cbk;
   stream_cbk_context = yac_host->yacContextCreate();

   // Allocate 32bit float input buffer
   input_buf  = YAC_New_FloatArray();
   input_buf->yacArrayAlloc(TKPORTAUDIO_MAX_BUFSIZE / sizeof(float));

   // Allocate 32bit float output buffer
   output_buf = YAC_New_FloatArray();
   output_buf->yacArrayAlloc(TKPORTAUDIO_MAX_BUFSIZE / sizeof(float));

   ////int r;

   snd_pcm_format_t sndSmpFormat;

   sample_format = _outParams->sample_format;

   switch(_outParams->sample_format)
   {
      case paInt8:
         sndSmpFormat = SND_PCM_FORMAT_S8;
         break;

      default:
      case paInt16:
         sndSmpFormat = SND_PCM_FORMAT_S16;
         sample_format = paInt16;
         break;

      case paInt24:
         sndSmpFormat = SND_PCM_FORMAT_S24;
         break;

      case paFloat32:
         sndSmpFormat = SND_PCM_FORMAT_FLOAT;
         break;
   }

   sample_rate = _sampleRate;

   // Open device
   {
      int err;
      b_hw = (_periodSize > 0);

      if(b_hw && ((err = snd_pcm_open(&handle, PAR_DEVNAME_HW, SND_PCM_STREAM_PLAYBACK, 0)) < 0))
      {
         yac_host->printf("[---] PaStream<alsa>: snd_pcm_open(dev=\"%s\") failed (playback): err=%d(\"%s\").\n",
                          PAR_DEVNAME_HW,
                          err,
                          snd_strerror(err)
                          );
         return YAC_FALSE;
      }

      snd_pcm_uframes_t prefPeriodSize = _periodSize;
      int prefNumPeriods = 2;
      snd_pcm_uframes_t prefBufferSize = _bufferSize; //prefPeriodSize * prefNumPeriods;

#ifdef PANDORA_LOWLATENCY_HACK
      if(b_hw)
      {
         // Enable OpenPandora per-stream low latency hack (thanks Notaz!)
         if(_periodSize < 256)
         {
            b_hw = loc_perform_hack(handle, (sUI)prefPeriodSize, 65536/*(sUI)prefBufferSize*/);

            if(!b_hw)
            {
               /* Try next period/buffer sizes */
               if(NULL != handle)
               {
                  snd_pcm_close(handle);
                  handle = NULL;
               }
               return YAC_FALSE;
            }
         }
      }
#endif // PANDORA_LOWLATENCY_HACK

      yac_host->printf("[dbg] PaStream<alsa>: bHW=%d, prefPeriodSize=%lu, prefNumPeriods=%d, prefBufferSize=%lu.\n",
                       b_hw,
                       prefPeriodSize,
                       prefNumPeriods,
                       prefBufferSize
                       );

      if(!b_hw)
      {
         /* Fallback to "default" device */
         if(NULL != handle)
         {
            snd_pcm_close(handle);
            handle = NULL;
         }

         if((err = snd_pcm_open(&handle, PAR_DEVNAME_DEF, SND_PCM_STREAM_PLAYBACK, 0)) < 0)
         {
            yac_host->printf("[---] PaStream<alsa>: snd_pcm_open(dev=\"%s\") failed (playback): err=%d(\"%s\").\n",
                             PAR_DEVNAME_DEF,
                             err,
                             snd_strerror(err)
                             );
            return YAC_FALSE;
         }

         /* Doesn't really matter, actual avgAvail is ~2048 */
         prefPeriodSize = 256;
         prefBufferSize = prefPeriodSize * prefNumPeriods;
      }

      if(!b_hw)
      {
         if ((err = snd_pcm_set_params(handle,
                                       sndSmpFormat,
                                       SND_PCM_ACCESS_RW_INTERLEAVED,
                                       2,
                                       sample_rate, // PAR_RATE
                                       1,
                                       // (long) (((prefPeriodSize/*PAR_LATENCY*/*1000000.0f) / float(sample_rate))) // usec
                                       (long) (((PAR_LATENCY*1000000.0f) / float(sample_rate))) // usec
                                       )) < 0) { 
            yac_host->printf("[---] PaStream<alsa>: snd_pcm_set_params() failed. err=%d(\"%s\")\n",
                             err,
                             snd_strerror(err)
                             );
            snd_pcm_close(handle);
            handle = NULL;
            return YAC_FALSE;
         }
      }

      int dir = -1;
      snd_pcm_uframes_t periodSize = prefPeriodSize;
      sUI periods = prefNumPeriods;
      snd_pcm_uframes_t bufferSize = prefBufferSize;

      // Query hardware parameters
      snd_pcm_hw_params_t *hwparams;
      snd_pcm_hw_params_alloca(&hwparams);

      if((err = snd_pcm_hw_params_any(handle,
                                      hwparams
                                      )) < 0)
      { 
         yac_host->printf("[---] PaStream<alsa>: snd_pcm_hw_params_any() failed. err=%d(\"%s\")\n",
                          err,
                          snd_strerror(err)
                          );
         snd_pcm_close(handle);
         handle = NULL;
         return YAC_FALSE;
      }

      if(b_hw)
      {

#if 1
         // Set access
         if((err = snd_pcm_hw_params_set_access(handle,
                                                hwparams,
                                                SND_PCM_ACCESS_RW_INTERLEAVED
                                                )) < 0)
         {
            yac_host->printf("[---] PaStream<alsa>: snd_pcm_hw_params_set_access() failed. err=%d(\"%s\")\n",
                             err,
                             snd_strerror(err)
                             );
            snd_pcm_close(handle);
            handle = NULL;
            return YAC_FALSE;
         }
#endif

#if 1
         // Set sample format
         if((err = snd_pcm_hw_params_set_format(handle,
                                                hwparams,
                                                sndSmpFormat
                                                )) < 0)
         {
            yac_host->printf("[---] PaStream<alsa>: snd_pcm_hw_params_set_format() failed. err=%d(\"%s\")\n",
                             err,
                             snd_strerror(err)
                             );
            snd_pcm_close(handle);
            handle = NULL;
            return YAC_FALSE;
         }
#endif

#if 1
         // Query min/max rates
         {
            sUI rateMin = 0;
            sUI rateMax = 0;

            if((err = snd_pcm_hw_params_get_rate_min(hwparams,
                                                     &rateMin,
                                                     &dir
                                                     )) < 0)
            {
               yac_host->printf("[---] PaStream<alsa>: snd_pcm_hw_params_get_rate_min() failed. err=%d(\"%s\")\n",
                                err,
                                snd_strerror(err)
                                );
            }

            if((err = snd_pcm_hw_params_get_rate_max(hwparams,
                                                     &rateMax,
                                                     &dir
                                                     )) < 0)
            {
               yac_host->printf("[---] PaStream<alsa>: snd_pcm_hw_params_get_rate_max() failed. err=%d(\"%s\")\n",
                                err,
                                snd_strerror(err)
                                );
            }

            yac_host->printf("[dbg] PaStream<alsa>: rateMin=%u rateMax=%u\n", rateMin, rateMax);
         }
#endif

#if 1
         // Set rate
         if((err = snd_pcm_hw_params_set_rate_near(handle,
                                                   hwparams,
                                                   &sample_rate,
                                                   &dir
                                                   )) < 0)
         {
            yac_host->printf("[---] PaStream<alsa>: snd_pcm_hw_params_set_rate_near() failed. rate=%u, err=%d(\"%s\")\n",
                             sample_rate,
                             err,
                             snd_strerror(err)
                             );
         }
#endif


#if 1
         // Set channels
         if((err = snd_pcm_hw_params_set_channels(handle,
                                                  hwparams,
                                                  2
                                                  )) < 0)
         {
            yac_host->printf("[---] PaStream<alsa>: snd_pcm_hw_params_set_channels() failed. err=%d(\"%s\")\n",
                             err,
                             snd_strerror(err)
                             );
            snd_pcm_close(handle);
            handle = NULL;
            return YAC_FALSE;
         }
#endif 

#if 1
         // Query min/max buffer size
         snd_pcm_uframes_t bufferSizeMin;
         snd_pcm_uframes_t bufferSizeMax;

         if((err = snd_pcm_hw_params_get_buffer_size_min(hwparams, &bufferSizeMin)))
         {
            yac_host->printf("[---] snd_pcm_hw_params_get_buffer_size_min() failed. err=%d(\"%s\")\n",
                             err,
                             snd_strerror(err)
                             );
         }

         if((err = snd_pcm_hw_params_get_buffer_size_max(hwparams, &bufferSizeMax)))
         {
            yac_host->printf("[~~~] snd_pcm_hw_params_get_buffer_size_max() failed. err=%d(\"%s\")\n",
                             err,
                             snd_strerror(err)
                             );
         }

         yac_host->printf("[dbg] PaStream<alsa>::open: bufSizeMin=%u bufSizeMax=%u\n",
                          (sUI)bufferSizeMin, (sUI)bufferSizeMax
                          );
#endif

#if 1
         // Set preferred buffer size
         if ((err = snd_pcm_hw_params_set_buffer_size(handle,
                                                      hwparams,
                                                      prefBufferSize
                                                      )) < 0)
         {
            yac_host->printf("[---] snd_pcm_hw_params_set_buffer_size() failed. prefBufferSize=%u, err=%d(\"%s\")\n",
                             prefBufferSize,
                             err,
                             snd_strerror(err)
                             );
         }
#endif

#if 1
         // Query min/max period size
         snd_pcm_uframes_t periodSizeMin;
         snd_pcm_uframes_t periodSizeMax;

         if((err = snd_pcm_hw_params_get_period_size_min(hwparams, &periodSizeMin, 0)))
         {
            yac_host->printf("[---] snd_pcm_hw_params_get_period_size_min() failed. err=%d(\"%s\")\n",
                             err,
                             snd_strerror(err)
                             );
         }

         if((err = snd_pcm_hw_params_get_period_size_max(hwparams, &periodSizeMax, 0)))
         {
            yac_host->printf("[---] snd_pcm_hw_params_get_period_size_max() failed. err=%d(\"%s\")\n",
                             err,
                             snd_strerror(err)
                             );
         }

         printf("[dbg] PaStream<alsa>::open: periodSizeMin=%lu periodSizeMax=%lu\n",
                periodSizeMin, periodSizeMax
                );
#endif

#if 1
         // Query #periods
         if ((err = snd_pcm_hw_params_get_periods_min(hwparams,
                                                      &periods,
                                                      &dir
                                                      )) < 0)
         {
            yac_host->printf("[---] snd_pcm_hw_params_get_periods_min() failed. err=%d(\"%s\")\n",
                             err,
                             snd_strerror(err)
                             );
         }
         else
         {
            yac_host->printf("[dbg] PaStream<alsa>::open: snd_pcm_hw_params_get_periods_min() returned %u\n",
                             periods
                             );
         }


         if ((err = snd_pcm_hw_params_get_periods_max(hwparams,
                                                      &periods,
                                                      &dir
                                                      )) < 0)
         {
            yac_host->printf("[---] snd_pcm_hw_params_get_periods_max() failed. err=%d(\"%s\")\n",
                             err,
                             snd_strerror(err)
                             );
         }
         else
         {
            yac_host->printf("[dbg] PaStream<alsa>::open: snd_pcm_hw_params_get_periods_max() returned %u\n",
                             periods
                             );
         }
#endif

#if 1
         // Set preferred #periods
         if ((err = snd_pcm_hw_params_set_periods(handle,
                                                  hwparams,
                                                  prefNumPeriods,
                                                  0
                                                  )) < 0)
         {
            yac_host->printf("[---] snd_pcm_hw_params_set_periods() failed. err=%d(\"%s\")\n",
                             err,
                             snd_strerror(err)
                             );
         }
#endif

#if 1
         // Set preferred period size
         if ((err = snd_pcm_hw_params_set_period_size(handle,
                                                      hwparams,
                                                      prefPeriodSize,
                                                      0
                                                      )) < 0)
         {
            yac_host->printf("[---] snd_pcm_hw_params_set_period_size() failed. prefPeriodSize=%lu err=%d(\"%s\")\n",
                             prefPeriodSize,
                             err,
                             snd_strerror(err)
                             );
         }
#endif


#if 1
         if(!hwParamsSet(hwparams, __LINE__))
         {
            return YAC_FALSE;
         }
#endif


         // if(!hwParamsSet(hwparams, __LINE__))
         // {
         //    return YAC_FALSE;
         // }

#if 1
         if(!hwParamsGet(hwparams, __LINE__))
         {
            return YAC_FALSE;
         }
#endif



#if 1
         // Query current period size
         if((err = snd_pcm_hw_params_get_period_size(hwparams, &periodSize, 0)))
         {
            yac_host->printf("[---] snd_pcm_hw_params_get_period_size() failed. err=%d(\"%s\")\n",
                             err,
                             snd_strerror(err)
                             );
         }
         else
         {
            yac_host->printf("[dbg] snd_pcm_hw_param_get_period_size() returned %lu.\n",
                             periodSize
                             );
         }
#endif


#if 1
         // Query actual #periods
         {
            int dir = -1;
            if ((err = snd_pcm_hw_params_get_periods(hwparams,
                                                     &periods,
                                                     &dir
                                                     )) < 0)
            {
               yac_host->printf("[---] snd_pcm_hw_params_get_periods() failed. err=%d(\"%s\")\n",
                                err,
                                snd_strerror(err)
                                );
            }
            else
            {
               yac_host->printf("[dbg] snd_pcm_hw_params_get_periods(): periods=%u\n", periods);
            }
         }
#endif


#if 0
         // Determine minimum buffer size
         {
            while(bufferSize < 65536)
            {
               sBool bPOT;
#ifdef PAR_TEST_SIZEPOT
               bPOT = (0 == (bufferSize & (bufferSize - 1u)));
#else
               bPOT = YAC_TRUE;
#endif
               if(bPOT && (err = snd_pcm_hw_params_test_buffer_size(handle,
                                                                    hwparams,
                                                                    bufferSize
                                                                    )) == 0)
               {
                  yac_host->printf("[dbg] PaStream<alsa>::open: minimum test buffer size is %lu\n", bufferSize);
                  break;
               }
               else
               {
                  // yac_host->printf("[---] snd_pcm_hw_params_test_buffer_size() failed. bufferSize=%lu, err=%d(\"%s\")\n",
                  //                  bufferSize,
                  //                  err,
                  //                  snd_strerror(err)
                  //                  );
                  // bufferSize = bufferSize << 1;
                  bufferSize = bufferSize + 1;
               }
            }

            if(2048 <= bufferSize)
            {
               yac_host->printf("[---] PaStream<alsa>::open: latency too high (bufferSize=%lu), aborting.\n",
                                bufferSize
                                );
               snd_pcm_close(handle);
               handle = NULL;
               return YAC_FALSE;
            }
         }


         if ((err = snd_pcm_hw_params_set_buffer_size(handle,
                                                      hwparams,
                                                      bufferSize
                                                      )) < 0)
         { 
            yac_host->printf("[---] snd_pcm_hw_params_set_buffer_size() failed. bufferSize=%lu, err=%d(\"%s\")\n",
                             bufferSize,
                             err,
                             snd_strerror(err)
                             );
            snd_pcm_close(handle);
            handle = NULL;
            return YAC_FALSE;
         }

#if 0
         if(!hwParamsSet(hwparams, __LINE__))
         {
            return YAC_FALSE;
         }

         if(!hwParamsGet(hwparams, __LINE__))
         {
            return YAC_FALSE;
         }
#endif
#endif

         // Query actual buffer size
         if ((err = snd_pcm_hw_params_get_buffer_size(hwparams,
                                                      &bufferSize
                                                      )) < 0) { 
            yac_host->printf("[---] snd_pcm_hw_params_get_buffer_size() failed. err=%d(\"%s\")\n",
                             err,
                             snd_strerror(err)
                             );
            snd_pcm_close(handle);
            handle = NULL;
            return YAC_FALSE;
         }
         else
         {
            yac_host->printf("[dbg] snd_pcm_hw_params_get_buffer_size() returned bufferSize=%lu\n",
                             bufferSize
                             );
         }





         // Determine minimum period size
         if(0)
         {
            periodSize = 8;//prefPeriodSize;

            while(periodSize <= bufferSize)
            {
               sBool bPOT;
#ifdef PAR_TEST_SIZEPOT
               bPOT = (0 == (periodSize & (periodSize - 1u)));
#else
               bPOT = YAC_TRUE;
#endif
               if(bPOT && (err = snd_pcm_hw_params_test_period_size(handle,
                                                                    hwparams,
                                                                    periodSize,
                                                                    0
                                                                    )) == 0)
               { 
                  yac_host->printf("[dbg] PaStream<alsa>::open: minimum test period size is %u\n", (sUI)periodSize);
                  if ((err = snd_pcm_hw_params_set_period_size(handle,
                                                               hwparams,
                                                               periodSize,
                                                               0
                                                               )) < 0)
                  {
                     yac_host->printf("[---] snd_pcm_hw_params_set_period_size() failed. err=%d(\"%s\")\n",
                                      err,
                                      snd_strerror(err)
                                      );
                     snd_pcm_close(handle);
                     handle = NULL;
                     return YAC_FALSE;
                  }
                  break;
               }
               else
               {
                  // printf("[---] snd_pcm_hw_params_test_period_size() failed. periodSize=%lu, err=%d(\"%s\")\n",
                  //        periodSize,
                  //        err,
                  //        snd_strerror(err)
                  //        );
                  ////periodSize = periodSize << 1;
                  ////periodSize = periodSize + (snd_pcm_uframes_t)prefPeriodSize;
                  periodSize = periodSize + 1;
               }
            }

            if(periodSize > bufferSize)
            {
               yac_host->printf("[---] PaStream<alsa>::open: cannot find a supported periodSize (bufSize=%u)\n",
                                (sUI)bufferSize
                                );
               snd_pcm_close(handle);
               handle = NULL;
               return YAC_FALSE;
            }

#if 0
            if(!hwParamsSet(hwparams, __LINE__))
            {
               return YAC_FALSE;
            }

            if(!hwParamsGet(hwparams, __LINE__))
            {
               return YAC_FALSE;
            }
#endif
         }




#if 1
         {
            int dir = 0;
            if ((err = snd_pcm_hw_params_get_periods(hwparams,
                                                     &periods,
                                                     &dir
                                                     )) < 0)
            {
               yac_host->printf("[---] snd_pcm_hw_params_get_periods() failed. err=%d(\"%s\")\n",
                                err,
                                snd_strerror(err)
                                );
               snd_pcm_close(handle);
               handle = NULL;
               return YAC_FALSE;
            }
         }

         yac_host->printf("[dbg] PaStream<alsa>::open: #periods=%u\n", periods);
#endif

#if 0
         if(!hwParamsSet(hwparams, __LINE__))
         {
            return YAC_FALSE;
         }

         if(!hwParamsGet(hwparams, __LINE__))
         {
            return YAC_FALSE;
         }
#endif



#if 1
         // Query rate
         if((err = snd_pcm_hw_params_get_rate(hwparams,
                                              &sample_rate,
                                              &dir
                                              )) < 0)
         {
            yac_host->printf("[---] PaStream<alsa>: snd_pcm_hw_params_get_rate() failed. err=%d(\"%s\")\n",
                             err,
                             snd_strerror(err)
                             );
            snd_pcm_close(handle);
            handle = NULL;
            return YAC_FALSE;
         }
         else
         {
            yac_host->printf("[dbg] PaStream<alsa>: snd_pcm_hw_params_get_rate(): sample_rate=%u.\n",
                             sample_rate
                             );
         }
#endif

      } // if(b_hw)
      else
      {
         // Default device (test_*_size() works)
#if 1
         // Determine minimum buffer size
         {
            bufferSize = 128;

            while(bufferSize < 65536)
            {
               sBool bPOT;
#ifdef PAR_TEST_SIZEPOT
               bPOT = (0 == (bufferSize & (bufferSize - 1u)));
#else
               bPOT = YAC_TRUE;
#endif
               if(bPOT && (err = snd_pcm_hw_params_test_buffer_size(handle,
                                                                    hwparams,
                                                                    bufferSize
                                                                    )) == 0)
               {
                  yac_host->printf("[dbg] PaStream<alsa>::open: minimum test buffer size is %lu\n", bufferSize);
                  break;
               }
               else
               {
                  // yac_host->printf("[---] snd_pcm_hw_params_test_buffer_size() failed. bufferSize=%lu, err=%d(\"%s\")\n",
                  //                  bufferSize,
                  //                  err,
                  //                  snd_strerror(err)
                  //                  );
                  // bufferSize = bufferSize << 1;
                  bufferSize = bufferSize + 1;
               }
            }

            if(2048 <= bufferSize)
            {
               yac_host->printf("[---] PaStream<alsa>::open: latency too high (bufferSize=%lu), aborting.\n",
                                bufferSize
                                );
               snd_pcm_close(handle);
               handle = NULL;
               return YAC_FALSE;
            }
         }


         if ((err = snd_pcm_hw_params_set_buffer_size(handle,
                                                      hwparams,
                                                      bufferSize
                                                      )) < 0)
         { 
            yac_host->printf("[---] snd_pcm_hw_params_set_buffer_size() failed. bufferSize=%lu, err=%d(\"%s\")\n",
                             bufferSize,
                             err,
                             snd_strerror(err)
                             );
            snd_pcm_close(handle);
            handle = NULL;
            return YAC_FALSE;
         }
#endif

         // Determine minimum period size
         if(1)
         {
            periodSize = 64;//prefPeriodSize;

            while(periodSize <= bufferSize)
            {
               sBool bPOT;
#ifdef PAR_TEST_SIZEPOT
               bPOT = (0 == (periodSize & (periodSize - 1u)));
#else
               bPOT = YAC_TRUE;
#endif
               if(bPOT && (err = snd_pcm_hw_params_test_period_size(handle,
                                                                    hwparams,
                                                                    periodSize,
                                                                    0
                                                                    )) == 0)
               { 
                  yac_host->printf("[dbg] PaStream<alsa>::open: minimum test period size is %u\n", (sUI)periodSize);
                  if ((err = snd_pcm_hw_params_set_period_size(handle,
                                                               hwparams,
                                                               periodSize,
                                                               0
                                                               )) < 0)
                  {
                     yac_host->printf("[---] snd_pcm_hw_params_set_period_size() failed. err=%d(\"%s\")\n",
                                      err,
                                      snd_strerror(err)
                                      );
                     snd_pcm_close(handle);
                     handle = NULL;
                     return YAC_FALSE;
                  }
                  break;
               }
               else
               {
                  // printf("[---] snd_pcm_hw_params_test_period_size() failed. periodSize=%lu, err=%d(\"%s\")\n",
                  //        periodSize,
                  //        err,
                  //        snd_strerror(err)
                  //        );
                  ////periodSize = periodSize << 1;
                  ////periodSize = periodSize + (snd_pcm_uframes_t)prefPeriodSize;
                  periodSize = periodSize + 1;
               }
            }

            if(periodSize > bufferSize)
            {
               yac_host->printf("[---] PaStream<alsa>::open: cannot find a supported periodSize (bufSize=%u)\n",
                                (sUI)bufferSize
                                );
               snd_pcm_close(handle);
               handle = NULL;
               return YAC_FALSE;
            }

         }

#if 0
         // (note) setting the configuration causes async__cbk to only be called 2 times */
         if(!hwParamsSet(hwparams, __LINE__))
         {
            return YAC_FALSE;
         }

         if(!hwParamsGet(hwparams, __LINE__))
         {
            return YAC_FALSE;
         }
#endif

         // Query actual period size
         if ((err = snd_pcm_hw_params_get_period_size(hwparams,
                                                      &periodSize,
                                                      &dir
                                                      )) < 0) { 
            yac_host->printf("[---] snd_pcm_hw_params_get_period_size() failed. err=%d(\"%s\")\n",
                             err,
                             snd_strerror(err)
                             );
            snd_pcm_close(handle);
            handle = NULL;
            return YAC_FALSE;
         }
         else
         {
            yac_host->printf("[dbg] PaStream<alsa>: tested periodSize=%lu\n",
                             periodSize
                             );
         }

         // Query actual buffer size
         if ((err = snd_pcm_hw_params_get_buffer_size(hwparams,
                                                      &bufferSize
                                                      )) < 0) { 
            yac_host->printf("[---] snd_pcm_hw_params_get_buffer_size() failed. err=%d(\"%s\")\n",
                             err,
                             snd_strerror(err)
                             );
            snd_pcm_close(handle);
            handle = NULL;
            return YAC_FALSE;
         }
         else
         {
            yac_host->printf("[dbg] PaStream<alsa>: tested bufferSize=%lu\n",
                             bufferSize
                             );
         }

         // Query actual #periods
         {
            int dir = 0;
            if ((err = snd_pcm_hw_params_get_periods(hwparams,
                                                     &periods,
                                                     &dir
                                                     )) < 0)
            {
               yac_host->printf("[---] snd_pcm_hw_params_get_periods() failed. err=%d(\"%s\")\n",
                                err,
                                snd_strerror(err)
                                );
               snd_pcm_close(handle);
               handle = NULL;
               return YAC_FALSE;
            }
         }

         yac_host->printf("[dbg] PaStream<alsa>::open: tested #periods=%u\n", periods);

      } // else if(b_hw)


      // Set up stream callback
      {
         snd_async_handler_t *ahandler;

         err = snd_async_add_pcm_handler(&ahandler, handle, async__cbk, (void*)this);

         if(err < 0)
         {
            yac_host->printf("[---] PaStream<alsa>::open: snd_async_add_pcm_handler() failed. err=%d(\"%s\")\n",
                             err,
                             snd_strerror(err)
                             );
            snd_pcm_close(handle);
            handle = NULL;
            return YAC_FALSE;
         }
      }

      yac_host->printf("[dbg] PaStream<alsa>::open: bufSize=%u\n",
                       (sUI)bufferSize
                       );

      yac_host->printf("[dbg] PaStream<alsa>::open: bufSize=%u, periodSize=%u, #periods=%u.\n", 
                       (sUI)bufferSize, 
                       (sUI)periodSize,
                       (sUI)periods
                       );

      // Remember period size for async callback
      period_size = periodSize;
      num_periods = periods;

      if((0 == num_periods) && (0 != period_size))
      {
         num_periods = bufferSize / period_size;

         if(num_periods <= 1)
         {
            num_periods = 2;
         }

         yac_host->printf("[~~~] PaStream<alsa>::open: num_periods==0, forcing num_periods=%u/%u=%u\n",
                          bufferSize, period_size, num_periods
                          );
      }
      else if(num_periods <= 1)
      {
         num_periods = 2;

         yac_host->printf("[~~~] PaStream<alsa>::open: num_periods<=1, forcing num_periods=2\n");
      }

      if( (err = snd_pcm_prepare(handle)) < 0)
      {
         yac_host->printf("[---] PaStream<alsa>::open: snd_pcm_prepare() failed. err=%d(\"%s\")\n",
                          err,
                          snd_strerror(err)
                          );
         snd_pcm_close(handle);
         handle = NULL;
         return YAC_FALSE;
      }
   }

//    printf("[dbg] #frag=%d latency=%d samples (%f millisec) rate=%d\n", 
//           PAR_FRAG, 
//           PAR_LATENCY, 
//           (((float)PAR_LATENCY) / PAR_RATE) * 1000,
//           PAR_RATE
//           );



   // Create worker thread
   {
      if(0 == pthread_create(&pthread_id, NULL, pastream__threadentry, (void*)this))
      {
         printf("[dbg] PaStream<alsa>::initStream: thread started.\n");

         return YAC_TRUE;
      }
      else
      {
         printf("[---] PaStream<alsa>::initStream: pthread_create() failed.\n");
         snd_pcm_close(handle);
         handle = NULL;
         return YAC_FALSE;
      }
   }
}

sBool _PaStream::initStream(void *_cbk,
                            _PaStreamParameters *_inParams,
                            _PaStreamParameters *_outParams,
                            sUI _sampleRate
                           ) 
{
   if(!initStream2(_cbk, _inParams, _outParams, _sampleRate,
                   64, 128
                   ))
   {
      // if(!initStream2(_cbk, _inParams, _outParams, _sampleRate,
      //                 128, 256
      //                 ))
      {
         // if(!initStream2(_cbk, _inParams, _outParams, _sampleRate,
         //                 256, 512
         //                 ))
         {
            if(!initStream2(_cbk, _inParams, _outParams, _sampleRate,
                            320, 640
                            ))
            {
               return initStream2(_cbk, _inParams, _outParams, _sampleRate,
                                  0, 0
                                  );
            }
         }
      }
   }

   return YAC_TRUE;
}

sSI _PaStream::pcmAvailUpdateRecover(void) {
   sSI avail = (sSI)snd_pcm_avail_update(handle);

   if(avail < 0)
   {
      snd_pcm_recover(handle, avail/*err*/, 1/*silent*/);
      
      avail = (sSI)snd_pcm_avail_update(handle);
      
      if(avail < 0)
      {
         yac_host->printf("[---] snd_pcm_recover<avail> failed: err=%d\n", (int)avail);
      }
   }

   return avail;
}

void _PaStream::_close(void) {
   if(b_running)
   {
      if(NULL != handle)
      {
         _stop();

         waitProcessCbk();

         b_running = YAC_FALSE;

         // Wait until worker thread has exited
         pthread_join(pthread_id, NULL);
         
         // Close sound device
         snd_pcm_close(handle);
         handle = NULL;
       
         printf("[dbg] PaStream<alsa>::close: thread terminated.\n");
      }

      if(NULL != stream_cbk_context)
      {
         yac_host->yacContextDestroy(stream_cbk_context);
      }
      
      YAC_DELETE_SAFE(input_buf);
      
      YAC_DELETE_SAFE(output_buf);
   }
}

void _PaStream::_start(void) {
   if(NULL != handle)
   {

#if 0
      ::gettimeofday(&tv_last, NULL);
#endif

      if(!b_hw)
      {
         memset(out_buf, 0, TKPORTAUDIO_MAX_BUFSIZE);
         for(sUI i=0; i<num_periods; i++)
         {
            snd_pcm_writei(handle, out_buf, period_size);
         }
      }

      num_left = 0;

      if(!b_hw)
      {
         int err;

         if((err=snd_pcm_start(handle))<0)
         {
            yac_host->printf("[---] PaStream<alsa>::start: snd_pcm_start() failed. err=%d(\"%s\")\n",
                             err,
                             snd_strerror(err)
                             );
         }
         else
         {
            // ok
            b_active = YAC_TRUE;
            yac_host->printf("[...] PaStream<alsa>::start: stream started.\n");
         }
      }
      else
      {
         // Thread will call snd_pcm_start()  (workaround)
         b_active = YAC_TRUE;
      }
   }
}

void _PaStream::waitProcessCbk(void) {
   sUI waitCnt = 0;

   while(b_processing_cbk)
   {
      usleep(1000);

      if(waitCnt++ > 2000)
      {
         printf("[---] PaStream::stop: async_cbk still busy after 2s!\n");
         break;
      }
   }
}

void _PaStream::_stop(void) {
   if(NULL != handle)
   {
      if(b_active)
      {
         b_active = YAC_FALSE;
        
#if 1
         int err;
         if((err=snd_pcm_drop(handle))<0)
         {
            printf("[---] PaStream<alsa>::stop: snd_pcm_drop() failed. err=%d(\"%s\")\n", err, snd_strerror(err));
         }
#endif
         
         usleep(250*1000); // why ?

#if 0
         int err;

         if((err=snd_pcm_pause(handle, 1))<0)
         {
            printf("[---] PaStream<alsa>::stop: snd_pcm_pause() failed. err=%d(\"%s\")\n", err, snd_strerror(err));
         }
         else
         {
            // ok
            printf("[...] PaStream<alsa>::stop: stream stopped.\n");
         }


         waitProcessCbk();
         
         usleep(250*1000); // why ?
#endif
      }
   }
}

void _PaStream::_abort(void) {
}

sSI _PaStream::_isStopped(void) {
   return b_active;
}

sSI _PaStream::_isActive(void) {
   return (NULL != handle);
}

sF32 _PaStream::_getCpuLoad(void) {
   return 0.0f; // (todo)
}

sSI _PaStream::_getNumInputChannels(void) {
   return 0;//PAR_CHANNELS;
}

sSI _PaStream::_getNumOutputChannels(void) {
   return PAR_CHANNELS;
}

sF32 _PaStream::_getInputLatency(void) {
   if(NULL != handle)
   {
      return (period_size / (sF32)sample_rate);
   }
   else
   {
      return 0.0f;
   }
}

sF32 _PaStream::_getOutputLatency(void) {
   if(NULL != handle)
   {
      return (period_size / (sF32)sample_rate);
   }
   else
   {
      return 0.0f;
   }
}

sF32 _PaStream::_getSampleRate(void) {
   if(NULL != handle)
   {
      return (sF32)sample_rate;
   }
   else
   {
      return 0.0f;
   }
}
