// ----------------------------------------------------------------------------
//
//  Copyright (C) 2006-2022 Fons Adriaensen <fons@linuxaudio.org>
//    
//  This program 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.
//
//  This program 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.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------


#include <endian.h>
#include <stdint.h>
#include <byteswap.h>
#include <sys/time.h>
#include "zita-alsa-pcmi.h"


// Public members ----------------------------------------------------------------------


int zita_alsa_pcmi_major_version (void)
{
    return ZITA_ALSA_PCMI_MAJOR_VERSION;
}


int zita_alsa_pcmi_minor_version (void)
{
    return ZITA_ALSA_PCMI_MINOR_VERSION;
}


Alsa_pcmi::Alsa_pcmi (const char        *play_name,
                      const char        *capt_name,
                      const char        *ctrl_name,
                      unsigned int       fsamp,
                      unsigned int       fsize,
                      unsigned int       nfrag,
                      unsigned int       debug) :
    _fsamp (fsamp),
    _fsize (fsize),
    _nfrag (nfrag),   
    _debug (debug),
    _state (STATE_FAIL),
    _play_handle (0),
    _capt_handle (0),
    _ctrl_handle (0),
    _play_hwpar (0),
    _play_swpar (0),
    _capt_hwpar (0),
    _capt_swpar (0),
    _play_nchan (0),
    _capt_nchan (0),
    _play_xrun (0),
    _capt_xrun (0),
    _synced (false),
    _play_npfd (0),
    _capt_npfd (0)
{
    const char *p;

    p = getenv ("ZITA_ALSA_PCMI_DEBUG");
    if (p && *p) _debug = atoi (p);
    initialise (play_name, capt_name, ctrl_name);
}


Alsa_pcmi::~Alsa_pcmi (void)
{
    if (_play_handle) snd_pcm_close (_play_handle);
    if (_capt_handle) snd_pcm_close (_capt_handle);
    if (_ctrl_handle) snd_ctl_close (_ctrl_handle); 
    if (_capt_swpar) snd_pcm_sw_params_free (_capt_swpar);
    if (_capt_hwpar) snd_pcm_hw_params_free (_capt_hwpar);
    if (_play_swpar) snd_pcm_sw_params_free (_play_swpar);
    if (_play_hwpar) snd_pcm_hw_params_free (_play_hwpar);
}


int Alsa_pcmi::pcm_start (void)
{
    unsigned int i, j, n;
    int          err;

    if (_play_handle)
    {
        if ((err = snd_pcm_prepare (_play_handle)) < 0)
        {
            if (_debug & DEBUG_STAT) fprintf (stderr, "Alsa_pcmi: pcm_prepare(play): %s\n",
                                              snd_strerror (err));
	    _state = STATE_FAIL;
            return -1;
	}
        n = snd_pcm_avail_update (_play_handle);
        if (n != _fsize * _nfrag)
        {
            if (_debug & DEBUG_STAT) fprintf  (stderr, "Alsa_pcmi: full buffer not available at start.\n");
            _state = STATE_FAIL;
            return -1;
        }
        for (i = 0; i < _nfrag; i++)
        {     
            play_init (_fsize);
            for (j = 0; j < _play_nchan; j++) clear_chan (j, _fsize);
            play_done (_fsize);
        }
        if ((err = snd_pcm_start (_play_handle)) < 0)
        {
            if (_debug & DEBUG_STAT) fprintf (stderr, "Alsa_pcmi: pcm_start(play): %s.\n", snd_strerror (err));
	    _state = STATE_FAIL;
            return -1;
        }
    }
    if (_capt_handle && !_synced)
    {
        if ((err = snd_pcm_prepare (_capt_handle)) < 0)
        {
            if (_debug & DEBUG_STAT) fprintf (stderr, "Alsa_pcmi: pcm_prepare(capt): %s\n",
                                              snd_strerror (err));
	    _state = STATE_FAIL;
            return -1;
	}
	if ((err = snd_pcm_start (_capt_handle)) < 0)
        {
            if (_debug & DEBUG_STAT) fprintf (stderr, "Alsa_pcmi: pcm_start(capt): %s.\n", snd_strerror (err));
            _state = STATE_FAIL;
            return -1;
	}
    }
    return 0;
}


int Alsa_pcmi::pcm_stop (void)
{
    int err;

    if (_play_handle && ((err = snd_pcm_drop (_play_handle)) < 0))
    {
        if (_debug & DEBUG_STAT) fprintf (stderr, "Alsa_pcmi: pcm_drop(play): %s.\n", snd_strerror (err));
	_state = STATE_FAIL;
        return -1;
    }
    if (_capt_handle && !_synced && ((err = snd_pcm_drop (_capt_handle)) < 0))
    {
        if (_debug & DEBUG_STAT) fprintf (stderr, "Alsa_pcmi: pcm_drop(capt): %s.\n", snd_strerror (err));
	_state = STATE_FAIL;
        return -1;
    }
    return 0;
}


snd_pcm_sframes_t Alsa_pcmi::pcm_wait (void)
{
    bool              need_capt;
    bool              need_play;
    snd_pcm_sframes_t capt_av;
    snd_pcm_sframes_t play_av;
    unsigned short    rev;
    int               i, r, n1, n2;

    if (_state != STATE_OPEN) return 0;
    
    need_capt = _capt_handle ? true : false;
    need_play = _play_handle ? true : false;

    while (need_play || need_capt)
    {
        n1 = 0;
        if (need_play)
        {
            snd_pcm_poll_descriptors (_play_handle, _poll_fd, _play_npfd);
            n1 += _play_npfd;
        }
        n2 = n1;
        if (need_capt)
        {
            snd_pcm_poll_descriptors (_capt_handle, _poll_fd + n1, _capt_npfd);
            n2 += _capt_npfd;
        }
        for (i = 0; i < n2; i++) _poll_fd [i].events |= POLLERR;

        r = poll (_poll_fd, n2, 1000);
        if (r < 0)
        {
            if (errno == EINTR) return 0;
            if (_debug & DEBUG_WAIT) fprintf (stderr, "Alsa_pcmi: poll(): %s\n.", strerror (errno));
            return 0;
        }
        if (r == 0)
        { 
            if (_debug & DEBUG_WAIT) fprintf (stderr, "Alsa_pcmi: poll timed out.\n");
            return 0;
        }               

        if (need_play)
        {
            snd_pcm_poll_descriptors_revents (_play_handle, _poll_fd, n1, &rev);
            if (rev & POLLERR)
            {
                if (_debug & DEBUG_WAIT) fprintf (stderr, "Alsa_pcmi: error on playback pollfd.\n");
                recover ();
                return 0;
            }
            if (rev & POLLOUT) need_play = false;
        }
        if (need_capt)
        {
            snd_pcm_poll_descriptors_revents (_capt_handle, _poll_fd + n1, n2 - n1, &rev);
            if (rev & POLLERR)
            {
                if (_debug & DEBUG_WAIT) fprintf (stderr, "Alsa_pcmi: error on capture pollfd.\n");
                recover ();
                return 0;
            }
            if (rev & POLLIN) need_capt = false;
        }
    }

    play_av = 999999999;
    if (_play_handle && (play_av = snd_pcm_avail_update (_play_handle)) < 0)
    {
        recover ();
        return 0;
    }
    capt_av = 999999999;
    if (_capt_handle && (capt_av = snd_pcm_avail_update (_capt_handle)) < 0)
    {
        recover ();
        return 0;
    }
    return (capt_av < play_av) ? capt_av : play_av;
}


int Alsa_pcmi::pcm_idle (int len)
{
    unsigned int       i;
    snd_pcm_uframes_t  n, k;

    if (_capt_handle)
    {
        n = len;
        while (n)  
        {
            k = capt_init (n);
            capt_done (k);
            n -= k;
        }
    }
    if (_play_handle)
    {
        n = len;
        while (n)
        {
            k = play_init (n);
            for (i = 0; i < _play_nchan; i++) clear_chan (i, k);
            play_done (k);
            n -= k;
        }
    }
    return 0;
}


int Alsa_pcmi::play_init (snd_pcm_uframes_t len)
{
    unsigned int                   i;
    const snd_pcm_channel_area_t   *a;
    int                            err;

    if ((err = snd_pcm_mmap_begin (_play_handle, &a, &_play_offs, &len)) < 0)
    {
        if (_debug & DEBUG_DATA) fprintf (stderr, "Alsa_pcmi: snd_pcm_mmap_begin(play): %s.\n", snd_strerror (err)); 
        return 0;
    }
    _play_step = (a->step) >> 3;
    for (i = 0; i < _play_nchan; i++, a++)
    {
        _play_ptr [i] = (char *) a->addr + ((a->first + a->step * _play_offs) >> 3);
    } 
   
    return len;
}


int Alsa_pcmi::capt_init (snd_pcm_uframes_t len)
{
    unsigned int                  i;
    const snd_pcm_channel_area_t  *a;
    int                           err;

    if ((err = snd_pcm_mmap_begin (_capt_handle, &a, &_capt_offs, &len)) < 0)
    {
        if (_debug & DEBUG_DATA) fprintf (stderr, "Alsa_pcmi: snd_pcm_mmap_begin(capt): %s.\n", snd_strerror (err)); 
        return 0;
    }
    _capt_step = (a->step) >> 3;

    for (i = 0; i < _capt_nchan; i++, a++)
    {
        _capt_ptr [i] = (char *) a->addr + ((a->first + a->step * _capt_offs) >> 3);
    } 

    return len;
}


void Alsa_pcmi::clear_chan (int chan, int len)
{
    _play_ptr [chan] = (this->*Alsa_pcmi::_clear_func)(_play_ptr [chan], len);
}


void Alsa_pcmi::play_chan (int chan, const float *src, int len, int step)
{
    _play_ptr [chan] = (this->*Alsa_pcmi::_play_func)(src, _play_ptr [chan], len, step);
}


void Alsa_pcmi::capt_chan  (int chan, float *dst, int len, int step)
{
    _capt_ptr [chan] = (this->*Alsa_pcmi::_capt_func)(_capt_ptr [chan], dst, len, step);
}


int Alsa_pcmi::play_done (int len)
{
    return snd_pcm_mmap_commit (_play_handle, _play_offs, len);
}


int Alsa_pcmi::capt_done (int len)
{
    return snd_pcm_mmap_commit (_capt_handle, _capt_offs, len);
}


void Alsa_pcmi::printinfo (void)
{
    fprintf (stdout, "playback :");
    if (_play_handle)
    {
        fprintf (stdout, "\n  nchan  : %d\n", _play_nchan);
        fprintf (stdout, "  fsamp  : %d\n", _fsamp);
        fprintf (stdout, "  fsize  : %ld\n", _fsize);
        fprintf (stdout, "  nfrag  : %d\n", _nfrag);
        fprintf (stdout, "  format : %s\n", snd_pcm_format_name (_play_format));
    }
    else fprintf (stdout, " not enabled\n");
    fprintf (stdout, "capture  :");
    if (_capt_handle)
    {
        fprintf (stdout, "\n  nchan  : %d\n", _capt_nchan);
        fprintf (stdout, "  fsamp  : %d\n", _fsamp);
        fprintf (stdout, "  fsize  : %ld\n", _fsize);
        fprintf (stdout, "  nfrag  : %d\n", _nfrag);
        fprintf (stdout, "  format : %s\n", snd_pcm_format_name (_capt_format));
        if (_play_handle) fprintf (stdout, "%s\n", _synced ? "synced" : "not synced");
    }
    else fprintf (stdout, " not enabled\n");
}


// Private members ---------------------------------------------------------------------


void Alsa_pcmi::initialise (const char *play_name, const char *capt_name, const char *ctrl_name)
{
    unsigned int          fsamp;
    snd_pcm_uframes_t     fsize;
    unsigned int          nfrag;
    int                   err;
    int                   dir;
    snd_ctl_card_info_t  *card;

    if (play_name)
    {
        if (snd_pcm_open (&_play_handle, play_name, SND_PCM_STREAM_PLAYBACK, 0) < 0)
        {
            _play_handle = 0;
            if (_debug & DEBUG_INIT) fprintf (stderr, "Alsa_pcmi: Cannot open PCM device %s for playback.\n",
                                              play_name);
        }
    }
    
    if (capt_name)
    {
        if (snd_pcm_open (&_capt_handle, capt_name, SND_PCM_STREAM_CAPTURE, 0) < 0)
        {
            _capt_handle = 0;
            if (_debug & DEBUG_INIT) fprintf (stderr, "Alsa_pcmi: Cannot open PCM device %s for capture.\n",
                                              capt_name);
        }
    }

    if (! _play_handle && ! _capt_handle) return;

    if (ctrl_name)
    {
        snd_ctl_card_info_alloca (&card);

        if ((err = snd_ctl_open (&_ctrl_handle, ctrl_name, 0)) < 0)
        {
            if (_debug & DEBUG_INIT) fprintf  (stderr, "Alse_driver: ctl_open(): %s\n",
                                               snd_strerror (err));
            return;
        }
        if ((err = snd_ctl_card_info (_ctrl_handle, card)) < 0)
        {
            if (_debug & DEBUG_INIT) fprintf  (stderr, "Alsa_pcmi: ctl_card_info(): %s\n",
                                               snd_strerror (err));
            return;
        }
    }

    if (_play_handle)
    {
        if (snd_pcm_hw_params_malloc (&_play_hwpar) < 0)
        {
            if (_debug & DEBUG_INIT) fprintf (stderr, "Alsa_pcmi: can't allocate playback hw params\n");
            return;
        }
        if (snd_pcm_sw_params_malloc (&_play_swpar) < 0)
        {
            if (_debug & DEBUG_INIT) fprintf (stderr, "Alsa_pcmi: can't allocate playback sw params\n");
            return;
        }
        if (set_hwpar (_play_handle, _play_hwpar, "playback", &_play_nchan) < 0) return;
        if (set_swpar (_play_handle, _play_swpar, "playback") < 0) return;
    }
        
    if (_capt_handle)
    {
        if (snd_pcm_hw_params_malloc (&_capt_hwpar) < 0)
        {
            if (_debug & DEBUG_INIT) fprintf (stderr, "Alsa_pcmi: can't allocate capture hw params\n");
            return;
        }
        if (snd_pcm_sw_params_malloc (&_capt_swpar) < 0)
        {
            if (_debug & DEBUG_INIT) fprintf (stderr, "Alsa_pcmi: can't allocate capture sw params\n");
            return;
        }
        if (set_hwpar (_capt_handle, _capt_hwpar, "capture", &_capt_nchan) < 0) return;
        if (set_swpar (_capt_handle, _capt_swpar, "capture") < 0) return;
    }

    if (_play_handle)
    {
        if (snd_pcm_hw_params_get_rate (_play_hwpar, &fsamp, &dir) || (fsamp != _fsamp) || dir)
        {
            if (_debug & DEBUG_INIT) fprintf (stderr, "Alsa_pcmi: can't get requested sample rate for playback.\n"); 
            return;
        }
        if (snd_pcm_hw_params_get_period_size (_play_hwpar, &fsize, &dir) || (fsize != _fsize) || dir)
        {
            if (_debug & DEBUG_INIT) fprintf (stderr, "Alsa_pcmi: can't get requested period size for playback.\n"); 
            return;
        }
        if (snd_pcm_hw_params_get_periods (_play_hwpar, &nfrag, &dir) || (nfrag != _nfrag) || dir)
        {
            if (_debug & DEBUG_INIT) fprintf (stderr, "Alsa_pcmi: can't get requested number of periods for playback.\n"); 
            return;
        }

        snd_pcm_hw_params_get_format (_play_hwpar, &_play_format);
        snd_pcm_hw_params_get_access (_play_hwpar, &_play_access);

        switch (_play_format)
        {
        case SND_PCM_FORMAT_FLOAT_LE:
            _clear_func = &Alsa_pcmi::clear_32;     
#if __BYTE_ORDER == __LITTLE_ENDIAN
            _play_func  = &Alsa_pcmi::play_floatne;     
#else
            _play_func  = &Alsa_pcmi::play_floatre;     
#endif            
            break;

        case SND_PCM_FORMAT_FLOAT_BE:
            _clear_func = &Alsa_pcmi::clear_32;     
#if __BYTE_ORDER == __LITTLE_ENDIAN
            _play_func  = &Alsa_pcmi::play_floatre;     
#else
            _play_func  = &Alsa_pcmi::play_floatne;     
#endif            
            break;

        case SND_PCM_FORMAT_S32_LE:
            _clear_func = &Alsa_pcmi::clear_32;     
            _play_func  = &Alsa_pcmi::play_32le;     
            break;

        case SND_PCM_FORMAT_S32_BE:
            _clear_func = &Alsa_pcmi::clear_32;     
            _play_func  = &Alsa_pcmi::play_32be;
            break;

        case SND_PCM_FORMAT_S24_3LE:
            _clear_func = &Alsa_pcmi::clear_24;     
            _play_func  = &Alsa_pcmi::play_24le;     
            break;

        case SND_PCM_FORMAT_S24_3BE:
            _clear_func = &Alsa_pcmi::clear_24;     
            _play_func  = &Alsa_pcmi::play_24be;
            break;

        case SND_PCM_FORMAT_S16_LE:
            _clear_func = &Alsa_pcmi::clear_16;     
            _play_func  = &Alsa_pcmi::play_16le;     
            break;

        case SND_PCM_FORMAT_S16_BE:
            _clear_func = &Alsa_pcmi::clear_16;     
            _play_func  = &Alsa_pcmi::play_16be;     
            break;

        default:
            if (_debug & DEBUG_INIT) fprintf (stderr, "Alsa_pcmi: can't handle playback sample format.\n"); 
            return;
        }

        _play_npfd = snd_pcm_poll_descriptors_count (_play_handle);
    }

    if (_capt_handle)
    {
        if (snd_pcm_hw_params_get_rate (_capt_hwpar, &fsamp, &dir) || (fsamp != _fsamp) || dir)
        {
            if (_debug & DEBUG_INIT) fprintf (stderr, "Alsa_pcmi: can't get requested sample rate for capture.\n"); 
            return;
        }
        if (snd_pcm_hw_params_get_period_size (_capt_hwpar, &fsize, &dir) || (fsize != _fsize) || dir)
        {
            if (_debug & DEBUG_INIT) fprintf (stderr, "Alsa_pcmi: can't get requested period size for capture.\n"); 
            return;
        }
        if (snd_pcm_hw_params_get_periods (_capt_hwpar, &nfrag, &dir) || (nfrag != _nfrag) || dir)
        {
            if (_debug & DEBUG_INIT) fprintf (stderr, "Alsa_pcmi: can't get requested number of periods for capture.\n"); 
            return;
        }

        if (_play_handle) _synced = ! snd_pcm_link (_play_handle, _capt_handle);

        snd_pcm_hw_params_get_format (_capt_hwpar, &_capt_format);
        snd_pcm_hw_params_get_access (_capt_hwpar, &_capt_access);

        switch (_capt_format)
        {
        case SND_PCM_FORMAT_FLOAT_LE:
#if __BYTE_ORDER == __LITTLE_ENDIAN
            _capt_func  = &Alsa_pcmi::capt_floatne;
#else            
            _capt_func  = &Alsa_pcmi::capt_floatre;
#endif
            break;

        case SND_PCM_FORMAT_FLOAT_BE:
#if __BYTE_ORDER == __LITTLE_ENDIAN
            _capt_func  = &Alsa_pcmi::capt_floatre;
#else            
            _capt_func  = &Alsa_pcmi::capt_floatne;
#endif
            break;

        case SND_PCM_FORMAT_S32_LE:
            _capt_func  = &Alsa_pcmi::capt_32le;     
            break;

        case SND_PCM_FORMAT_S32_BE:
            _capt_func  = &Alsa_pcmi::capt_32be;     
            break;

        case SND_PCM_FORMAT_S24_3LE:
            _capt_func  = &Alsa_pcmi::capt_24le;     
            break;

        case SND_PCM_FORMAT_S24_3BE:
            _capt_func  = &Alsa_pcmi::capt_24be;     
            break;

        case SND_PCM_FORMAT_S16_LE:
            _capt_func  = &Alsa_pcmi::capt_16le;     
            break;

        case SND_PCM_FORMAT_S16_BE:
            _capt_func  = &Alsa_pcmi::capt_16be;     
            break;

        default:
            if (_debug & DEBUG_INIT) fprintf (stderr, "Alsa_pcmi: can't handle capture sample format.\n"); 
            return;
        }

        _capt_npfd = snd_pcm_poll_descriptors_count (_capt_handle);
    }

    if (_play_npfd + _capt_npfd > MAXPFD)
    {
        if (_debug & DEBUG_INIT) fprintf (stderr, "Alsa_pcmi: interface requires more than %d pollfd\n", MAXPFD);
        return;
    }      

    _state = STATE_OPEN;
}       


int Alsa_pcmi::set_hwpar (snd_pcm_t *handle,  snd_pcm_hw_params_t *hwpar, const char *sname, unsigned int *nchan)
{
    bool err;

    if (snd_pcm_hw_params_any (handle, hwpar) < 0)
    {
        if (_debug & DEBUG_INIT) fprintf (stderr, "Alsa_pcmi: no %s hw configurations available.\n",
                                          sname);
        return -1;
    }
    if (snd_pcm_hw_params_set_periods_integer (handle, hwpar) < 0)
    {
        if (_debug & DEBUG_INIT) fprintf (stderr, "Alsa_pcmi: can't set %s periods to integral value.\n",
                                          sname);
        return -1;
    }
    if (   (snd_pcm_hw_params_set_access (handle, hwpar, SND_PCM_ACCESS_MMAP_NONINTERLEAVED) < 0)
        && (snd_pcm_hw_params_set_access (handle, hwpar, SND_PCM_ACCESS_MMAP_INTERLEAVED) < 0)
        && (snd_pcm_hw_params_set_access (handle, hwpar, SND_PCM_ACCESS_MMAP_COMPLEX) < 0))
    {
        if (_debug & DEBUG_INIT) fprintf (stderr, "Alsa_pcmi: the %s interface doesn't support mmap-based access.\n",
                                          sname);
        return -1;
    }
    if (_debug & FORCE_16B)
    {
        err =    (snd_pcm_hw_params_set_format (handle, hwpar, SND_PCM_FORMAT_S16_LE) < 0)
              && (snd_pcm_hw_params_set_format (handle, hwpar, SND_PCM_FORMAT_S16_BE) < 0);
    }
    else
    {
        err =    (snd_pcm_hw_params_set_format (handle, hwpar, SND_PCM_FORMAT_FLOAT_LE) < 0)
              && (snd_pcm_hw_params_set_format (handle, hwpar, SND_PCM_FORMAT_FLOAT_BE) < 0)
              && (snd_pcm_hw_params_set_format (handle, hwpar, SND_PCM_FORMAT_S32_LE) < 0)
              && (snd_pcm_hw_params_set_format (handle, hwpar, SND_PCM_FORMAT_S32_BE) < 0)
              && (snd_pcm_hw_params_set_format (handle, hwpar, SND_PCM_FORMAT_S24_3LE) < 0)
              && (snd_pcm_hw_params_set_format (handle, hwpar, SND_PCM_FORMAT_S24_3BE) < 0)
              && (snd_pcm_hw_params_set_format (handle, hwpar, SND_PCM_FORMAT_S16_LE) < 0)
              && (snd_pcm_hw_params_set_format (handle, hwpar, SND_PCM_FORMAT_S16_BE) < 0);
    }
    if (err)
    {
        if (_debug & DEBUG_INIT) fprintf (stderr, "Alsa_pcmi: no supported sample format on %s interface.\n",
                                          sname);
        return -1;
    }
    if (snd_pcm_hw_params_set_rate (handle, hwpar, _fsamp, 0) < 0)
    {
        if (_debug & DEBUG_INIT) fprintf (stderr, "Alsa_pcmi: can't set %s sample rate to %u.\n",
                                          sname, _fsamp);
        return -1;
    }
    snd_pcm_hw_params_get_channels_max (hwpar, nchan);
    if (*nchan > 1024)
    { 
        if (_debug & DEBUG_INIT) fprintf (stderr, "Alsa_pcmi: detected more than 1024 %s channnels, reset to 2.\n",
                                         sname);
        *nchan = 2;     
    }                           
    if (_debug & FORCE_2CH) 
    {
        *nchan = 2;
    }
    if (*nchan > MAXCHAN)
    { 
        if (_debug & DEBUG_INIT) fprintf (stderr, "Alsa_pcmi: number of %s channels reduced to %d.\n",
                                         sname, MAXCHAN);
        *nchan = MAXCHAN;     
    }                           

    if (snd_pcm_hw_params_set_channels (handle, hwpar, *nchan) < 0)
    {
        if (_debug & DEBUG_INIT) fprintf (stderr, "Alsa_pcmi: can't set %s channel count to %u.\n",
                                          sname, *nchan);
        return -1;
    }
    if (snd_pcm_hw_params_set_period_size (handle, hwpar, _fsize, 0) < 0)
    {
        if (_debug & DEBUG_INIT) fprintf (stderr, "Alsa_pcmi: can't set %s period size to %lu.\n",
                                          sname, _fsize);
        return -1;
    }
    if (snd_pcm_hw_params_set_periods (handle, hwpar, _nfrag, 0) < 0)
    {
        if (_debug & DEBUG_INIT) fprintf (stderr, "Alsa_pcmi: can't set %s periods to %u.\n",
                                 sname, _nfrag);
        return -1;
    }
    if (snd_pcm_hw_params_set_buffer_size (handle, hwpar, _fsize * _nfrag) < 0)
    {
        if (_debug & DEBUG_INIT) fprintf (stderr, "Alsa_pcmi: can't set %s buffer length to %lu.\n",
                                          sname, _fsize * _nfrag);
        return -1;
    }
    if (snd_pcm_hw_params (handle, hwpar) < 0)
    {
        if (_debug & DEBUG_INIT) fprintf (stderr, "Alsa_pcmi: can't set %s hardware parameters.\n",
                                          sname);
        return -1;
    }

    return 0;
}


int Alsa_pcmi::set_swpar (snd_pcm_t *handle, snd_pcm_sw_params_t *swpar, const char *sname) 
{
    int err;
    
    snd_pcm_sw_params_current (handle, swpar);

    if ((err = snd_pcm_sw_params_set_tstamp_mode (handle, swpar, SND_PCM_TSTAMP_MMAP)) < 0)
    {
        if (_debug & DEBUG_INIT) fprintf (stderr, "Alsa_pcmi: can't set %s timestamp mode to %u.\n",
                                          sname, SND_PCM_TSTAMP_MMAP);
        return -1;
    }
    if ((err = snd_pcm_sw_params_set_start_threshold (handle, swpar, 0)) < 0)
    {
        if (_debug & DEBUG_INIT) fprintf (stderr, "Alsa_pcmi: can't set %s start_threshold to 0.\n",
                                          sname);
        return -1;
    }
    if ((err = snd_pcm_sw_params_set_avail_min (handle, swpar, _fsize)) < 0)
    {
        if (_debug & DEBUG_INIT) fprintf (stderr, "Alsa_pcmi: can't set %s avail_min to %lu.\n",
                                          sname, _fsize);
        return -1;
    }
    if ((err = snd_pcm_sw_params (handle, swpar)) < 0)
    {
        if (_debug & DEBUG_INIT) fprintf (stderr, "Alsa_pcmi: can't set %s software parameters.\n",
                                          sname);
        return -1;
    }

    return 0;
}


int Alsa_pcmi::recover (void)
{
    int                err; 
    snd_pcm_status_t   *stat;

    snd_pcm_status_alloca (&stat);

    if (_play_handle)
    {
        if ((err = snd_pcm_status (_play_handle, stat)) < 0)
        {
            if (_debug & DEBUG_STAT) fprintf (stderr, "Alsa_pcmi: pcm_status(play): %s\n",
                                             snd_strerror (err));
        }
        _play_xrun = xruncheck (stat);
    }
    if (_capt_handle)
    {
        if ((err = snd_pcm_status (_capt_handle, stat)) < 0)
        {
            if (_debug & DEBUG_STAT) fprintf (stderr, "Alsa_pcmi: pcm_status(capt): %s\n",
                                              snd_strerror (err));
        }
        _capt_xrun = xruncheck (stat);
    }

    return pcm_stop () || pcm_start ();
}       


float Alsa_pcmi::xruncheck (snd_pcm_status_t *stat)
{
    struct timeval   tupd, trig;
    int              ds, du;

    if (snd_pcm_status_get_state (stat) == SND_PCM_STATE_XRUN)
    {
        snd_pcm_status_get_tstamp (stat, &tupd);
        snd_pcm_status_get_trigger_tstamp (stat, &trig);
        ds = tupd.tv_sec - trig.tv_sec;
        du = tupd.tv_usec - trig.tv_usec;
        if (du < 0)
        {
            du += 1000000;
            ds -= 1;
        }
        return ds + 1e-6f * du;
    }
    return 0.0f;
}


char *Alsa_pcmi::clear_16 (char *dst, int nfrm)
{
    while (nfrm--)
    {
        *((short int *) dst) = 0;
        dst += _play_step;
    }  
    return dst;
}

char *Alsa_pcmi::clear_24 (char *dst, int nfrm)
{
    while (nfrm--)
    {
        dst [0] = 0;
        dst [1] = 0;
        dst [2] = 0;
        dst += _play_step;
    }
    return dst;  
}

char *Alsa_pcmi::clear_32 (char *dst, int nfrm)
{
    while (nfrm--)
    {
        *((int *) dst) = 0;
        dst += _play_step;
    }
    return dst;  
}


char *Alsa_pcmi::play_16le (const float *src, char *dst, int nfrm, int step)
{
    float     s;
    short int d;

    while (nfrm--)
    {
        s = *src;
        if      (s >  1) d = 0x7fff;
        else if (s < -1) d = 0x8001;
        else d = (short int)((float) 0x7fff * s); 
        dst [0] = d;
        dst [1] = d >> 8;
        dst += _play_step;
        src += step;
    } 
    return dst; 
}

char *Alsa_pcmi::play_16be (const float *src, char *dst, int nfrm, int step)
{
    float     s;
    short int d;

    while (nfrm--)
    {
        s = *src;
        if      (s >  1) d = 0x7fff;
        else if (s < -1) d = 0x8001;
        else d = (short int)((float) 0x7fff * s); 
        dst [0] = d >> 8;
        dst [1] = d;
        dst += _play_step;
        src += step;
    } 
    return dst; 
}

char *Alsa_pcmi::play_24le (const float *src, char *dst, int nfrm, int step)
{
    float   s;
    int     d;

    while (nfrm--)
    {
        s = *src;
        if      (s >  1) d = 0x007fffff;
        else if (s < -1) d = 0x00800001;  
        else d = (int)((float) 0x007fffff * s); 
        dst [0] = d;
        dst [1] = d >> 8;
        dst [2] = d >> 16;
        dst += _play_step;
        src += step;
    }  
    return dst;
}

char *Alsa_pcmi::play_24be (const float *src, char *dst, int nfrm, int step)
{
    float   s;
    int     d;

    while (nfrm--)
    {
        s = *src;
        if      (s >  1) d = 0x007fffff;
        else if (s < -1) d = 0x00800001;  
        else d = (int)((float) 0x007fffff * s); 
        dst [0] = d >> 16;
        dst [1] = d >> 8;
        dst [2] = d;
        dst += _play_step;
        src += step;
    }  
    return dst;
}

char *Alsa_pcmi::play_32le (const float *src, char *dst, int nfrm, int step)
{
    float   s;
    int     d;

    while (nfrm--)
    {
        s = *src;
        if      (s >  1) d = 0x007fffff;
        else if (s < -1) d = 0x00800001;  
        else d = (int)((float) 0x007fffff * s); 
        dst [0] = 0;
        dst [1] = d;
        dst [2] = d >> 8;
        dst [3] = d >> 16;
        dst += _play_step;
        src += step;
    }  
    return dst;
}

char *Alsa_pcmi::play_32be (const float *src, char *dst, int nfrm, int step)
{
    float   s;
    int     d;

    while (nfrm--)
    {
        s = *src;
        if      (s >  1) d = 0x007fffff;
        else if (s < -1) d = 0x00800001;  
        else d = (int)((float) 0x007fffff * s); 
        dst [0] = d >> 16;
        dst [1] = d >> 8;
        dst [2] = d;
        dst [3] = 0;
        dst += _play_step;
        src += step;
    }  
    return dst;
}

char *Alsa_pcmi::play_floatne (const float *src, char *dst, int nfrm, int step)
{
    while (nfrm--)
    {
        *((float *) dst) = *src;
        dst += _play_step;
        src += step;
    }  
    return dst;
}

char *Alsa_pcmi::play_floatre (const float *src, char *dst, int nfrm, int step)
{
    uint32_t *s = (uint32_t *) src;

    while (nfrm--)
    {
        *((uint32_t *) dst) = bswap_32 (*s);
        dst += _play_step;
        s += step;
    }  
    return dst;
}


const char *Alsa_pcmi::capt_16le (const char *src, float *dst, int nfrm, int step)
{
    int16_t s;

    while (nfrm--)
    {
        s =  (src [0] & 0xFF);
        s += (src [1] & 0xFF) << 8;  
        *dst = (float) s / (float) 0x7fff;  
        dst += step;
        src += _capt_step;
    }  
    return src;
}

const char *Alsa_pcmi::capt_16be (const char *src, float *dst, int nfrm, int step)
{
    int16_t s;

    while (nfrm--)
    {
        s =  (src [0] & 0xFF) << 8;
        s += (src [1] & 0xFF);  
        *dst = (float) s / (float) 0x7fff;  
        dst += step;
        src += _capt_step;
    }  
    return src;
}

const char *Alsa_pcmi::capt_24le (const char *src, float *dst, int nfrm, int step)
{
    int32_t s;

    while (nfrm--)
    {
        s  = (src [0] & 0xFF);
        s += (src [1] & 0xFF) << 8;  
        s += (src [2] & 0xFF) << 16;
        if (s & 0x00800000) s-= 0x01000000;  
        *dst = (float) s / (float) 0x007fffff;  
        dst += step;
        src += _capt_step;
    } 
    return src;
}

const char *Alsa_pcmi::capt_24be (const char *src, float *dst, int nfrm, int step)
{
    int32_t s;

    while (nfrm--)
    {
        s  = (src [0] & 0xFF) << 16;
        s += (src [1] & 0xFF) << 8;  
        s += (src [2] & 0xFF);
        if (s & 0x00800000) s-= 0x01000000;  
        *dst = (float) s / (float) 0x007fffff;  
        dst += step;
        src += _capt_step;
    } 
    return src;
}

const char *Alsa_pcmi::capt_32le (const char *src, float *dst, int nfrm, int step)
{
    int32_t s;

    while (nfrm--)
    {
        s  = (src [0] & 0xFF) << 8;
        s += (src [1] & 0xFF) << 16;  
        s += (src [2] & 0xFF) << 24;
        *dst = (float) s / (float) 0x7fffff00;  
        dst += step;
        src += _capt_step;
    }  
    return src;
}

const char *Alsa_pcmi::capt_32be (const char *src, float *dst, int nfrm, int step)
{
    int32_t s;

    while (nfrm--)
    {
        s  = (src [0] & 0xFF) << 24;
        s += (src [1] & 0xFF) << 16;  
        s += (src [2] & 0xFF) << 8;
        *dst = (float) s / (float) 0x7fffff00;  
        dst += step;
        src += _capt_step;
    }  
    return src;
}

const char *Alsa_pcmi::capt_floatne (const char *src, float *dst, int nfrm, int step)
{
    while (nfrm--)
    {
        *dst = *((float *) src);  
        dst += step;
        src += _capt_step;
    }  
    return src;
}

const char *Alsa_pcmi::capt_floatre (const char *src, float *dst, int nfrm, int step)
{
    uint32_t *d = (uint32_t *) dst;
    
    while (nfrm--)
    {
        *d = bswap_32 (*((uint32_t *) src));
        d += step;
        src += _capt_step;
    }  
    return src;
}

